# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Akkorde expandieren

    Mit diesem Skript werden Akkordbezeichnungen in einer Partitur in eine eigene Akkordzeile expandiert.

    Rückmeldungen bitte an villpaul(a)bluewin.ch
<<<

History: 17.05.2004 - Erste Ausgabe
                    - Deutsche Akkordbezeichnung B -> Bb
                    - nur erste Stimme in Akkordzeile übernehmen
         20.05.2004 - Funktion Akkordnoten zusammenfassen implementiert (ohne 5/8 Takt)
         21.05.2004 - neue Funktion: Akkordzeile säubern; text, metafile, richText und lyric werden entfernt
                    - Instrumentenbezeichnung in Akkordzeile
         23.05.2004 - Ungerade Teilungen werden richtig zusammengefasst (Triolen, Quintolen,...)
                    - auch Violinschlüssel möglich
                    - Oktavlinie: Alle Akkorde oberhalb der Oktavlinie werden nach unten oktaviert
         26.05.2004 - Wenn Akkordzeile vorhanden, wird Dialogauswahl auf "Akkordzeile neu berechnen" gesetzt
         26.05.2004 - Berechnung mit Umkehrung und Oktavlinie korrigiert
         30.05.2004 - Pausen mit Akkorden werden berücksichtigt
                    - Neue Funktion "Akkorde sammeln" (Quantisierung auf '1/4')
                    - Layout der Akkordzeile wird zurückgesetzt
                    - Umkehrakkorde werden in die richtige Oktave versetzt
         31.05.2004 - Fehler bei Oktavlinie > c
         02.06.2004 - Fehler in Akkordumsetzung Cmaj7(#11), Cm(sus4), C6/9
         12.03.2005 - Notation ohne Takt wird beim Akkorde - Zusammenfassen
                      --> wird wie 4/4 behandelt
         29.08.2007 - Script-Internationalisierung
         15.03.2008   Berücksichtigung von 3-fach Punktierung
         12.06.2008 - Akkordzeile als erste Notenzeile einfuegen
                    - Akkordzeile verbergen
                    - Noten ohne Wert entfernen
         01.11.2008 - Nur Grundakkorde ausgeben (baseChords)
                    - Grundnote oktavieren (octaveChord)

         

        ToDo:   - Aufteilen in zwei Stimmen
                - Auftakte nicht umsetzen, oder durch Pausen ersetzen


"""
german = ("de", {
    'convert1stStaff'   :   '1ste Notenzeile in Akkordzeile umwandeln',
    'collectChordsInSystem':'Akkorde im System sammeln',
    'recalculateChordLine': 'Akkordzeile neu berechnen',
    'deleteChordLine'   :   'Akkordzeile löschen',
    'additionalFunctions':  'Zusatzfunktionen',
    'combineChordNotes' :   'Akkordnoten zusammenfassen',
    'cleanChordLine'    :   'Akkordzeile säubern (Text, Liedtext, ... )',
    'removeChords'      :   'Akkordbezeichnungen aus Akkordzeile entfernen',
    'setBassLine'       :   'Alle Noten zwischen Oktavlinie und Oktave setzen',
    'octaveLine'        :   'Oktavlinie',
    'clef'              :   'Schlüssel',
    'clefTreble'        :   'Violinschlüssel',
    'clefBass'          :   'Bassschlüssel',
    'octave'            :   '    Oktave    ',
    'dialogHeader'      :   'Akkorde expandieren',
    'regUndo'           :   'Akkorde expandieren',
    'setAs1stStaff'     :   'Akkordzeile als erste Notenzeile einfügen',
    'hideChordLine'     :   'Akkordzeile verbergen',
    'baseChords'        :   'Nur Grundakkorde',
    'octaveChord'       :   'Grundnote oktavieren'

    } )

try:
    exec('from %s import translations' % ( translationModule() ))
    translations.append(german)
    setLanguages(translations)
except:
    def tr(s):
        return german[1].get(s, "???")
#-------------------------------------------------------------------


from xml.dom.minidom import NodeList, Node, Element
from caplib.rational import Rational
from caplib.capDOM import ScoreChange
import tempfile, string, new

diatonicNote = {'c':    (0,0),
           'cis':   (0,1), 'c#':(0,1),
           'des':   (1,1),'db':(1,1),
           'd':     (1,2),
           'dis':   (1,3),'d#':(1,3),
           'es':    (2,3), 'eb':(2,3),
           'e':     (2,4),
           'f':     (3,5),
           'fis':   (3,6),'f#':(3,6),
           'ges':   (4,6),'gb':(4,6),
           'g':     (4,7),
           'gis':   (4,8),'g#':(4,8),
           'as':    (5,8),'ab':(5,8),
           'a':     (5,9),
           'ais':   (5,10),'a#':(5,10),
           'bb':    (6,10),'hes':(6,10),
           'b':     (6,11),'h':(6,11)}

class parameter:
    def __init__(self):
        self.bassLine       = True
        self.bassLineOffset = 'fis'
        self.bassLineDiatonic, self.bassLineChromatic = diatonicNote.get(self.bassLineOffset, (None,None))
        self.actionSelect=0
        self.cleanNotes = True
        self.combineNotes = True
        self.removeAccords = False
        self.octave=4
        self.key = 1  # Bassschlüssel
        self.insertFirstLine = False
        self.makeInvisible = False
        self.baseChords = False
        self.octaveChord =False

dlgParameter = parameter()

# Gibt alle Elemente auf der nächsten Ebene zurück
def getChildElements(self, filter = ''):
    childElements = NodeList()
    for child in self.childNodes:
        if child.nodeType == child.ELEMENT_NODE:
            if filter == '':
                childElements.append(child)
            elif child.tagName == filter:
                childElements.append(child)
    return childElements

Node.getChildElements = new.instancemethod(getChildElements,None,Node)

def gotoChild(self, name, new=False):
    newEl = None
    if new:
        pass
    else:
        for child in self.childNodes:
            if child.nodeType == child.ELEMENT_NODE and child.tagName == name:
                newEl = child
                break
    if newEl == None:
        newEl = doc.createElement(name)
        self.appendChild(newEl)
    return newEl
Node.gotoChild = new.instancemethod(gotoChild,None,Node)

def getDlgParameter():
    action = Radio([tr('convert1stStaff'),          # 0
                    tr('collectChordsInSystem'),    # 1
                    tr('recalculateChordLine'),     # 2
                    tr('deleteChordLine'),          # 3
                    tr('additionalFunctions')       # 4
                    ],value = dlgParameter.actionSelect)

    combineNotes = CheckBox( tr('combineChordNotes'), value=dlgParameter.combineNotes)
    cleanNotes = CheckBox( tr('cleanChordLine'), value=dlgParameter.cleanNotes)
    removeAccords = CheckBox( tr('removeChords'), value=dlgParameter.removeAccords)
    bassLine = CheckBox( tr('setBassLine'), value=dlgParameter.bassLine, padding = 12)
    setFirstLine = CheckBox(tr('setAs1stStaff'), value = dlgParameter.insertFirstLine)
    setMakeInvisible = CheckBox(tr('hideChordLine'), value = dlgParameter.makeInvisible)
    setBaseChords = CheckBox(tr('baseChords'), value = dlgParameter.baseChords)
    setOctaveChord = CheckBox(tr('octaveChord'), value = dlgParameter.octaveChord)

    bassLineOffsetLabel = Label('Oktavlinie', width = 10)
    bassLineOffset = Edit(dlgParameter.bassLineOffset)
    hboxBassLineOffset = HBox([bassLineOffsetLabel,bassLineOffset ], padding = 12)
    

    labelKey = Label(tr('clef'), width = 10)    
    key = ComboBox([tr('clefTreble'), tr('clefBass')],value=1, width=15)
    octaveCorrection = ComboBox(['+1','0','-1'], value=1, width=5)
    labelOctaveCorrection = Label(tr('octave') )
    hboxKey = HBox([labelKey, key,labelOctaveCorrection,octaveCorrection ], padding = 12)

    leerZeile = Label('    ')

    vbox = VBox([action, leerZeile, setFirstLine, combineNotes, cleanNotes, removeAccords, setMakeInvisible, setBaseChords, setOctaveChord, leerZeile, hboxKey, hboxBassLineOffset, bassLine, leerZeile])

    
    dlg = Dialog(tr('dialogHeader'), vbox)
    if dlg.run():
        dlgParameter.actionSelect = action.value()
        dlgParameter.cleanNotes = cleanNotes.value()
        dlgParameter.combineNotes = combineNotes.value()
        dlgParameter.removeAccords = removeAccords.value()
        
        dlgParameter.bassLine = bassLine.value()
        dlgParameter.bassLineOffset = string.lower(bassLineOffset.value())
        dlgParameter.bassLineDiatonic, dlgParameter.bassLineChromatic = diatonicNote.get(dlgParameter.bassLineOffset, (None,None))
        if dlgParameter.bassLineDiatonic == None:
            dlgParameter.bassLine = False
        dlgParameter.key = key.value()
        if dlgParameter.key == 0:    # Violinschlüssel
            dlgParameter.octave = 5
        if dlgParameter.key == 1:    # Bassschlüssel
            dlgParameter.octave = 4
        dlgParameter.octave = dlgParameter.octave + [1,0,-1][octaveCorrection.value()]
        dlgParameter.makeInvisible = setMakeInvisible.value()
        dlgParameter.insertFirstLine = setFirstLine.value()
        dlgParameter.baseChords = setBaseChords.value()
        dlgParameter.octaveChord = setOctaveChord.value()
        
        
        return True
    else:
        return False

intervalle = []


def tone2step(a):
    acc = string.lower(a)
    step = None
    alt  = None
    if len(acc) >= 3:
        step, alt = diatonicNote.get(acc[0:3],(None,0))
        accRest = a[3:]
    if step == None and len(acc) >= 2:
        step, alt = diatonicNote.get(acc[0:2],(None,0))
        accRest = a[2:]
    if step == None and len(acc) >= 1:
        step, alt = diatonicNote.get(acc[0:1],(None,0))
        accRest = a[1:]
    if step == None:
        accRest = a
    accRest = string.strip(accRest)

    return (step, alt, accRest)

def intervalleAddRem(add=None,rem=None):
    global intervalle
    if add <> None and add not in intervalle:
        intervalle.append(add)
    if rem <> None and rem in intervalle:
        intervalle.remove(rem)

def getInterval(accord,onlyBaseAccords=False):
    global intervalle
    step, alt, acc2 = tone2step(accord)
    if step == None:
        return step, alt, []
    
    acc = string.lower(acc2)

    if onlyBaseAccords:
        intervalle = ['1','3','5']  # C-Dur Akkord
        if string.find(acc,'maj') == 0:
            passe
        elif string.find(acc,'m') == 0 or accord[0:1].islower():   # Moll Akkord
            intervalle = ['1','b3','5']    # Moll Akkord
        return (step, alt, intervalle)

    intervalle = ['1','3','5']  # C-Dur Akkord
    if string.find(acc,'maj') == 0:
        intervalle = ['1','3','5','7']
        if '7' in acc:
            pass
        elif '9' in acc:
            intervalle = ['1','3','5','7','9']
        elif '11' in acc:
            intervalle = ['1','3','5','7','9','11']
        elif '13' in acc:
            intervalle = ['1','3','5','7','9','11','13']
        elif '6' in acc:
            intervalle = ['1','3','5','6']
    elif string.find(acc,'m') == 0 or accord[0:1].islower():   # Moll Akkord
        intervalle = ['1','b3','5']
        if '6' in acc: intervalle.append('6')
        elif 'm7' in acc: intervalle.append('b7')
        elif 'm9' in acc: intervalle = intervalle +['b7','9']
        elif 'm11' in acc: intervalle = intervalle +['b7','9','11']
        elif 'm13' in acc: intervalle = intervalle +['b7','9','11','13']
        elif '9' in acc: intervalle.append('9')
        elif 'maj7' in acc: intervalle.append('7')

    elif string.find(acc,'6') == 0:
        intervalle.append('6')
        if '9' in acc: intervalle.append('9')
    elif string.find(acc,'7') == 0: 
        intervalle = ['1','3','5','b7']
        if '6' in acc: intervalle.append('13')   # C 7/6
    elif string.find(acc,'9') == 0:
        intervalle = ['1','3','5','b7','9']
    elif string.find(acc,'11') == 0:
        intervalle = ['1','3','5','b7','9','11']
    elif string.find(acc,'13') == 0:
        intervalle = ['1','3','5','b7','9','11','13']
    elif string.find(acc,'5') == 0:
        intervalle = ['1','5']

    if 'b5' in acc:
        intervalleAddRem('b5','5')
    elif '#5' in acc:
        intervalleAddRem('#5','5')

    if 'add9' in acc:
        intervalleAddRem('9')

    if 'b9' in acc:
        intervalleAddRem('b9','9')
    elif '#9' in acc:
        intervalleAddRem('#9','9')

    if '#11' in acc:
        intervalleAddRem('#11')

    if 'sus' in acc:
        intervalleAddRem('4','3')
        intervalleAddRem('4','b3')

    if 'aug' in acc:
        intervalleAddRem('#5','5')
        if '7' in acc:
            intervalleAddRem('b7','7')

    if 'dim' in acc:        
        intervalleAddRem('b5','5')
        intervalleAddRem('b3','3')
        if '7' in acc:
            intervalleAddRem('bb7','7')

    return (step, alt, intervalle)

# END getInterval() *****************************

interval2Diatonic ={'1':    (0,0),
                    'b3':   (2,3),
                    '3':    (2,4),
                    '4':    (3,5),
                    'b5':   (4,6),
                    '5':    (4,7),
                    '#5':   (4,8),
                    '6':    (5,9),
                    'bb7':  (6,9),
                    'b7':   (6,10),
                    '7':    (6,11),
                    'b9':   (8,13),
                    '9':    (8,14),
                    '#9':   (8,15),
                    '11':   (10,17),
                    '#11':  (10,18),
                    '13':   (12,21)
                    }

def getPitch(diatonic, chromatic, octave):
    ch = chromatic % 12
    di = diatonic % 7
    oct = octave + diatonic // 7
    pitch = 'CDEFGAB'[di] + str(oct)
    alter = ch - (0,2,4,5,7,9,11)[di]
    if alter > 6:     alter -= 12
    elif alter < -6:  alter += 12
    return pitch, alter

def getUmkehrung(accord,di,ch):
    umkehrung = ''
    pos = string.find(accord,'/')
    if pos >= 0:
        step, alt, acc2 = tone2step(accord[pos+1:])
        if step == None:
            return umkehrung
        step = (step-di) % 7
        alt = (alt-ch) % 12
        if step <> None:
            for (key, value) in interval2Diatonic.iteritems():
                if value == (step,alt):
                    umkehrung = key
                    break
    return umkehrung
    
    
def analyzeAkkord(chord):
    akkordText = ''
    for transposable in chord.getElementsByTagName('transposable'):
        base = transposable.getAttribute('base')
        for drawObj in transposable.getElementsByTagName('drawObj'):
            if drawObj.hasAttribute('base') and drawObj.getAttribute('base') == base:
                for text in drawObj.getElementsByTagName('text'):
                    t = text.getElementsByTagName('content')[0].firstChild.nodeValue
                    if 'capella' in text.getElementsByTagName('font')[0].getAttribute('face'):
                        if 'Q' in t: t = 'b'
                        elif 'S' in t: t = '#'
                    akkordText = akkordText + t
                # Korrektur für deutsche Akkordbezeichnung B -> Bb
                if base == 'Bb':
                    if string.lower(akkordText[0:1]) == 'b' and string.lower(akkordText[0:2]) <> 'bb':
                        akkordText = 'Bb' + akkordText[1:]
                break
        break
    return akkordText


def addElementNode(el,tagName):
    # add new Node to el if Node "tagName" does not exist
    # otherwise return the existing Node
    global doc
    childs = el.childNodes
    for n in range(childs.length):
        if childs[n].nodeType ==childs[n].ELEMENT_NODE and childs[n].tagName == tagName:
            return childs[n]
    newChild = doc.createElement(tagName)
    el.appendChild(newChild)
    return newChild
    
def addNewElementNode(el,tagName):
    # add new Node with tagName "tagName" to el 
    global doc
    newChild = doc.createElement(tagName)
    el.appendChild(newChild)
    return newChild

actAcc = ''
def voice2Accord(voice):
    defaultOctave = dlgParameter.octave
    global actAcc
    for noteObjects in voice.getElementsByTagName('noteObjects'):
        for noteObject in noteObjects.getChildElements():
            # Pause in Note umwandeln
            if noteObject.tagName == 'rest':
                transposable = noteObject.getElementsByTagName('transposable')
                if transposable.length > 0:
                    transposable = transposable[0].cloneNode(True)
                    newChord = doc.createElement('chord')
                    noteObjects.replaceChild(newChord, noteObject)
                    duration = noteObject.getElementsByTagName('duration')[0].cloneNode(True)
                    newChord.appendChild(duration)
                    drawObjects = addElementNode(newChord,'drawObjects')
                    drawObj = addElementNode(drawObjects,'drawObj')
                    drawObj.appendChild(transposable)
                    heads = addNewElementNode(newChord,'heads')
                    head = addNewElementNode(heads,'head')
                    head.setAttribute('pitch','G4')
                    noteObject = newChord
                    
            if noteObject.tagName == 'chord':
                newAcc = analyzeAkkord(noteObject)
                if newAcc <> '':
                    actAcc = newAcc
                if actAcc <> '':
                    # alle bestehenden Notenköpfe löschen
                    heads = addElementNode(noteObject, 'heads')
                    for head in heads.getElementsByTagName('head'):
                        head.parentNode.removeChild(head)

                    octAdd = 0 # default
                    diatAcc, chromAcc, accInterval = getInterval(actAcc, dlgParameter.baseChords)
                    accUmkehrung = getUmkehrung(actAcc, diatAcc, chromAcc)
                    if accUmkehrung <> ''  and accUmkehrung in accInterval:
                        diatUmk, chromUmk = interval2Diatonic[accUmkehrung]
                        if (chromAcc+chromUmk) % 12 > chromAcc:
                            octAdd += 1
                    else:
                        chromUmk = 0 # falls accUmkehrung nicht existiert

                    # falls gesamter Akkord über Basslinie dann eine Oktave tiefer
                    if (chromAcc+chromUmk) % 12 >= dlgParameter.bassLineChromatic and dlgParameter.bassLineChromatic > 0:
                        octAdd -= 1
                    headsUsed = []
                    for i in accInterval:
                        di1, ch1 = interval2Diatonic[i]
                        octUmk = 0
                        if accUmkehrung in accInterval:
                            di2, ch2 = interval2Diatonic[accUmkehrung]
                            if ch2 <= ch1:
                                octUmk = -1

                        # Basslinie
                        if dlgParameter.bassLine:
                            octUmk = 0
                            octAdd = 0
                            if chromAcc >= dlgParameter.bassLineChromatic:
                                if chromAcc+ch1 >= dlgParameter.bassLineChromatic + 12 :
                                    octUmk -= 1
                            else:
                                if chromAcc+ch1 < dlgParameter.bassLineChromatic:
                                    octUmk += 1
                        
                        newPitch, newAlter = getPitch(diatAcc + di1, chromAcc + ch1, defaultOctave+octUmk+octAdd)  # diatonisch, chromatisch, oktave

                        head = addNewElementNode(heads,'head')
                        head.setAttribute('pitch',newPitch)
                        if newAlter <> 0:
                            alter = addNewElementNode(head,'alter')
                            alter.setAttribute('step',str(newAlter))
                            
                        headsUsed.append((newPitch, newAlter))

                    # Akkordnote anhängen                            
                    if dlgParameter.octaveChord:
                        
                        newPitch, newAlter = getPitch(diatAcc, chromAcc, defaultOctave+octAdd)  # diatonisch, chromatisch, oktave
                        if (newPitch, newAlter) in headsUsed or newPitch[0] in  ['E','F']:
                            newPitch, newAlter = getPitch(diatAcc, chromAcc, defaultOctave+octAdd-1)  # diatonisch, chromatisch, oktave
                            

                        head = addNewElementNode(heads,'head')
                        head.setAttribute('pitch',newPitch)
                        if newAlter <> 0:
                            alter = addNewElementNode(head,'alter')
                            alter.setAttribute('step',str(newAlter))
                        
            
            

def getNoteObjects(voice):
    noteObject = voice.getElementsByTagName('noteObjects')[0]
    objList = noteObject.childNodes
    newList = NodeList()
    for n in range(objList.length):
        if objList[n].nodeType == objList[n].ELEMENT_NODE:
            newList.append(objList[n])
    return newList


def getDuration(chord):
    if chord.getElementsByTagName('duration').length == 0:
        return Rational('0')
    dur = chord.getElementsByTagName('duration')[0]
    duration = Rational(str(dur.getAttribute('base')))
    for tuplet in dur.getElementsByTagName('tuplet'):
        if tuplet.hasAttribute('count'):
            count = tuplet.getAttribute('count')
        else:
            count = '1'
        if tuplet.hasAttribute('tripartite') and tuplet.getAttribute('tripartite') == 'true':
            #                   0     1     2     3     4     5     6     7     8     9     10     11     12      13
            factor = Rational(['1/1','1/1','3/4','2/3','3/4','3/5','3/6','6/7','6/8','6/9','6/10','6/11','12/12','12/13','12/14','12/15'][eval(count)])
        else:
            factor = Rational(['1/1','1/1','2/2','2/3','4/4','4/5','4/6','4/7','8/8','8/9','8/10','8/11','8/12', '8/13', '8/14', '8/15' ][eval(count)])
        if tuplet.hasAttribute('prolong') and tuplet.getAttribute('prolong') == 'true':
            factor = factor * 2
        duration = duration * factor
        tuplet.setAttribute('count','0')   # Ausweg, wenn tuplet gelöscht wird, dann hängt capella !!
        tuplet.setAttribute('tripartite','false')
        tuplet.setAttribute('prolong','false')
        
        
    if dur.hasAttribute('dots'):
        d = dur.getAttribute('dots')
        if d == '1':
            duration = duration * Rational('3/2')
        elif d == '2':
            duration = duration * Rational('7/4')
        elif d == '3':
            duration = duration * Rational('15/8')
    return duration

def combineChords(voice):
        
    firstChord = None
    timeChord = Rational(0)
    timeBar = Rational(0)
    time = voice.parentNode.parentNode.getAttribute('defaultTime')
    if time == 'infinite' or time == 'C' or time == 'allaBreve':
        time = '4/4'
    defaultTime = Rational(str(time))
    childNodes = getNoteObjects(voice)        
    for childNode in childNodes:
        if childNode.nodeType == childNode.ELEMENT_NODE:
            if childNode.tagName == 'timeSign':
                time = childNode.getAttribute('time')
                if time == 'infinite' or time == 'C' or time == 'allaBreve':
                    time = '4/4'
                defaultTime = Rational(str(time))
                FirstChord = None
                timeBar = Rational(0)
            elif childNode.tagName == 'keySign':
                FirstChord = None
                timeBar = Rational(0)
            elif childNode.tagName == 'barline':
                FirstChord = None
                timeBar = Rational(0)
            elif childNode.tagName == 'chord':
                duration = getDuration(childNode)
                for tuplet in childNode.getElementsByTagName('tuplet'):
                    tuplet.setAttribute('count','0')
                
                timeBar = timeBar + duration
                if firstChord == None:
                    firstChord = childNode
                    timeChord = duration
                elif childNode.getElementsByTagName('transposable').length > 0:
                    firstChord = childNode
                    timeChord = duration
                else:
                    timeChord = timeChord + duration
                    childNode.parentNode.removeChild(childNode)
                    
            elif childNode.tagName == 'rest':
                duration = getDuration(childNode)

                timeBar = timeBar + duration
                if firstChord == None:
                    pass
                else:
                    timeChord = timeChord + duration
                    childNode.parentNode.removeChild(childNode)

            if firstChord <> None:
                dur = addElementNode(firstChord,'duration')
                dots = 0
                tc = timeChord
                while tc.p > 1 and tc.p % 2 == 1:
                    dots += 1
                    tc = tc - Rational(1, tc.q)
                dur.setAttribute('base',str(tc))
                dur.setAttribute('dots',str(dots))

            if timeBar >= defaultTime:
                firstChord = None
                timeBar = Rational(0)
                timeChord = Rational(0)

def collectAccords(system):
    noteObjList = []
    durationDict = dict()
    voiceDuration = Rational('0')
    for staff in system.getElementsByTagName('staff'):
        if 'Akkordzeile' == staff.getAttribute('layout'):
            continue
        for voice in staff.getElementsByTagName('voice'):
            durationPointer = Rational('0')
            for noteObjects in voice.getElementsByTagName('noteObjects'):
                for noteObj in noteObjects.getChildElements():
                    if noteObj.tagName in ['chord','rest']:
                        if noteObj.getElementsByTagName('transposable').length > 0:
                            noteObjList.append((durationPointer,noteObj))
                            durationDict[str(durationPointer)] = 0
                    if noteObj.tagName in ['barline','clefSign','keySign','timeSign']:
                        noteObjList.append((durationPointer,noteObj))
                        durationDict[str(durationPointer)] = 0
                    durationPointer = durationPointer + getDuration(noteObj)
            if voiceDuration < durationPointer:
                voiceDuration = durationPointer

    # gesammelte Akkorde der Reihe nach sortieren und in eine Liste ablegen    
    durationDictList =[]
    for key in durationDict.iterkeys():
        durationDictList.append(Rational(key))
    durationDictList.sort()
    noteObjList2 = []
    for duration in durationDictList:
        if duration == Rational('0'):
            signs = ['clefSign','keySign','timeSign','barline','chord','rest']
        else:
            signs = ['barline','clefSign','keySign','timeSign','chord','rest']
        for sign in signs:
            for dur,obj in noteObjList:
                if duration == dur and obj.tagName == sign:
                    noteObjList2.append((dur,obj))  
                    break
                                               
    # Gesammelte Akkorde in Akkordzeile aufreihen
    for staff in system.getElementsByTagName('staff'):
        if 'Akkordzeile' == staff.getAttribute('layout'):
            for voice in staff.getElementsByTagName('voice'):
                for noteObjects in voice.getElementsByTagName('noteObjects'):
                    # Alle Notenobjekte löschen
                    for noteObj in noteObjects.getChildElements():
                        noteObj.parentNode.removeChild(noteObj)
                    # Elemente mit Akkorden einfügen
                    durationPointer = Rational('0')
                    for dur, obj in noteObjList2:
                        while durationPointer < dur:
                            chord = addNewElementNode(noteObjects,'chord')
                            duration = addElementNode(chord,'duration')
                            duration.setAttribute('base','1/4')
                            durationPointer += Rational('1/4')
                            heads = addNewElementNode(chord,'heads')
                            head = addNewElementNode(heads,'head')
                            head.setAttribute('pitch','G4')
                        if obj.tagName in ['chord','rest']:
                            chord = addNewElementNode(noteObjects,'chord')
                            duration = addElementNode(chord,'duration')
                            duration.setAttribute('base','1/4')
                            durationPointer += Rational('1/4')
                            heads = addNewElementNode(chord,'heads')
                            head = addNewElementNode(heads,'head')
                            head.setAttribute('pitch','G4')
                            transposable = obj.getElementsByTagName('transposable')
                            if transposable.length > 0:
                                transposable = transposable[0].cloneNode(True)
                                drawObjects= addElementNode(chord,'drawObjects')
                                drawObj=addElementNode(drawObjects,'drawObj')
                                drawObj.appendChild(transposable)
                        else:
                            clone = obj.cloneNode(True)
                            noteObjects.appendChild(clone)
            # den rest der Zeile mit Noten auffüllen                    
            while durationPointer < voiceDuration:
                chord = addNewElementNode(noteObjects,'chord')
                duration = addElementNode(chord,'duration')
                duration.setAttribute('base','1/4')
                durationPointer += Rational('1/4')
                heads = addNewElementNode(chord,'heads')
                head = addNewElementNode(heads,'head')
                head.setAttribute('pitch','G4')
                                
                            

def changeDoc(score):
    found = False
    for system in score.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            if 'Akkordzeile' == staff.getAttribute('layout'):
                found = True
                dlgParameter.actionSelect = 2 # Auswahl auf "Akkordzeile neu berechnen" setzen
                break
        if found:
            break

    if not getDlgParameter():
        return False
    if dlgParameter.actionSelect in [0,1,2]:
        # Mustersystem um Akkordzeile erweitern
        found = False
        # im Mustersystem suchen, ob bereits eine "Akkordzeile" existiert
        for staffLayout in score.getElementsByTagName('staffLayout'):
            if 'Akkordzeile' == staffLayout.getAttribute('description'):
                found = True
                notation = staffLayout.getElementsByTagName('notation')[0]
                notation.setAttribute('defaultClef',['treble','bass'][dlgParameter.key])
                break
        if not found:
            # im Mustersystem 1ste Zeile duplizieren und als Akkordzeile anhängen
            staffLayout = score.getElementsByTagName('staffLayout')[0]
            newStaffLayout = staffLayout.cloneNode(True)
            if dlgParameter.insertFirstLine:
                staffLayout.parentNode.insertBefore(newStaffLayout, staffLayout)
            else:
                staffLayout.parentNode.appendChild(newStaffLayout)
                
            newStaffLayout.setAttribute('description','Akkordzeile')

            notation = newStaffLayout.getElementsByTagName('notation')[0]
            notation.setAttribute('defaultClef',['treble','bass'][dlgParameter.key])
            
            instrument = addElementNode(newStaffLayout,'instrument')
            instrument.setAttribute('name','Akkordzeile')
            instrument.setAttribute('abbrev','AZ')
            
            distances = addElementNode(newStaffLayout,'distances')
            distances.setAttribute('top','6')
            distances.setAttribute('bottom','6')
            if distances.hasAttribute('lineDist'):
                distances.removeAttribute('lineDist')

            sound = addElementNode(newStaffLayout,'sound')
            sound.setAttribute('percussion','false')
            sound.setAttribute('instr','0')  # Konzertflügel
            # sound.setAttribute('volume','127')  # Volumen
            
            
    
    if dlgParameter.actionSelect in [0,3]:
        # Akkordzeile löschen, bei "0" damit sie neu kopiert wird
        for staff in score.getElementsByTagName('staff'):
            if 'Akkordzeile' == staff.getAttribute('layout'):
                staff.parentNode.removeChild(staff)
    
        
    if dlgParameter.actionSelect in [0,1,2]:
        # Akkordzeilen in den Systemen einfügen
        for system in score.getElementsByTagName('system'):
            found = False
            for staff in system.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    for clefSign in staff.getElementsByTagName('clefSign'):
                        clefSign.setAttribute('clef',['treble','bass'][dlgParameter.key])
                    found = True
                    break
            if not found:
                staff = system.getElementsByTagName('staff')[0]
                newStaff = staff.cloneNode(True)

                if dlgParameter.insertFirstLine:
                    staff.parentNode.insertBefore(newStaff, staff)
                else:
                    staff.parentNode.appendChild(newStaff)

                newStaff.setAttribute('layout','Akkordzeile')
                for clefSign in newStaff.getElementsByTagName('clefSign'):
                    clefSign.setAttribute('clef',['treble','bass'][dlgParameter.key])
                for extraDistance in newStaff.getElementsByTagName('extraDistance'):
                    extraDistance.parentNode.removeChild(extraDistance)
                for voice in newStaff.getElementsByTagName('voice'):
                    if voice.hasAttribute('stemDir'):
                        voice.removeAttribute('stemDir')                        

    if dlgParameter.actionSelect in [1]:
        # Akkorde sammeln
        for system in score.getElementsByTagName('system'):
            collectAccords(system)
            # Schlüssel anpassen
            for staff in system.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    for clefSign in staff.getElementsByTagName('clefSign'):
                        clefSign.setAttribute('clef',['treble','bass'][dlgParameter.key])
                    break

    if dlgParameter.actionSelect in [0,1,2]:
        # Akkordzeilen in den Systemen konvertieren
        for system in score.getElementsByTagName('system'):
            for staff in system.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    # in Akkordzeile alle Stimmen ausser erster entfernen
                    for voice in staff.getElementsByTagName('voice')[1:]:
                        voice.parentNode.removeChild(voice)
                    # in Akkordzeile Akkorde erweitern
                    for voice in staff.getElementsByTagName('voice'):
                        voice2Accord(voice)        
                        break

    if dlgParameter.actionSelect in [0,1,2,4]:
        if dlgParameter.combineNotes:
            # Akkordnoten zusammenfassen
            for staff in score.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    for voice in staff.getElementsByTagName('voice'):
                        combineChords(voice)        
                        break

        if dlgParameter.removeAccords:
            # Akkordbezeichnungen in der Akkordzeile löschen
            for staff in score.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    for drawObjects in staff.getElementsByTagName('drawObjects'):
                        for transposable in drawObjects.getElementsByTagName('transposable'):
                            transposable.parentNode.parentNode.removeChild(transposable.parentNode)
                        if drawObjects.getElementsByTagName('drawObj').length == 0:
                            drawObjects.parentNode.removeChild(drawObjects)

        if dlgParameter.cleanNotes:
            # Akkordzeile säubern
            for staff in score.getElementsByTagName('staff'):
                if 'Akkordzeile' == staff.getAttribute('layout'):
                    for lyric in staff.getElementsByTagName('lyric'):
                        lyric.parentNode.removeChild(lyric)                    
                    for drawObjects in staff.getElementsByTagName('drawObjects'):
                        for child_1 in drawObjects.childNodes:
                            if child_1.nodeType == child_1.ELEMENT_NODE and child_1.tagName == 'drawObj':
                                for child_2 in child_1.childNodes:
                                    if child_2.nodeType == child_2.ELEMENT_NODE and child_2.tagName in ['text','metafile','richText','bracket']:
                                        child_1.parentNode.removeChild(child_1)
                        # Falls alle drawObj gelöscht wurden, auch drawObjects löschen
                        if drawObjects.getElementsByTagName('drawObj').length == 0:
                            drawObjects.parentNode.removeChild(drawObjects)
                    # Noten und Pausen ohne Wert entfernen
                    for chord in staff.getElementsByTagName('chord'):
                        for duration in chord.getElementsByTagName('duration'):
                            if duration.hasAttribute('noDuration'):
                                if duration.getAttribute('noDuration') == 'true':
                                    chord.parentNode.removeChild(chord)
                    for rest in staff.getElementsByTagName('rest'):
                        for duration in rest.getElementsByTagName('duration'):
                            if duration.hasAttribute('noDuration'):
                                if duration.getAttribute('noDuration') == 'true':
                                    rest.parentNode.removeChild(rest)
                                    

        if dlgParameter.makeInvisible:
            # Akkordzeile verbergen
            prevTop = 99
            prevBottom = 99
            clTop = 99
            clBottom = 99
            nextTop = 99
            nextBottom = 99
            layout = score.gotoChild('layout')
            found = False
            isFirst = True
            isLast = True
            for staffLayout in layout.getElementsByTagName('staffLayout'):
                distance = staffLayout.gotoChild('distances')
                top = distance.getAttribute('top')
                bottom = distance.getAttribute('bottom')
                if staffLayout.getAttribute('description') == 'Akkordzeile':
                    clTop = int(top)
                    clBottom = int(bottom)
                    found = True
                    notation = staffLayout.gotoChild('notation')
                    notation.setAttribute('notelines','           ')
                    instrument = staffLayout.gotoChild('instrument')
                    instrument.setAttribute('name','')
                    instrument.setAttribute('abbrev','')
                    
                elif not found:
                    prevTop = int(top)
                    prevBottom = int(bottom)
                    isFirst = False
                else:
                    nextTop = int(top)
                    nextBottom = int(bottom)
                    isLast = False
                    break
            for system in score.getElementsByTagName('system'):
                found=False
                for staff in system.getElementsByTagName('staff'):
                    if staff.getAttribute('layout') == 'Akkordzeile':
                        chordStaff = staff
                        found = True
                    elif found:
                        top = 0
                        for extraDistance in staff.getElementsByTagName('extraDistance'):
                            if extraDistance.hasAttribute('top'):
                                top = int(extraDistance.getAttribute('top'))
                        break
                    else:
                        bottom = 0
                        for extraDistance in staff.getElementsByTagName('extraDistance'):
                            if extraDistance.hasAttribute('bottom'):
                                bottom = int(extraDistance.getAttribute('bottom'))

                if chordStaff.getElementsByTagName('extraDistance'):
                    extraDistance = chordStaff.gotoChild('extraDistance')
                else:
                    extraDistance = doc.createElement('extraDistance')
                    for voices in chordStaff.getElementsByTagName('voices'):
                        chordStaff.insertBefore(extraDistance, voices)

                if isLast:
                    dist = clTop + prevBottom + bottom
                    extraDistance.setAttribute('top', str(-dist))
                else:
                    dist = clBottom + nextTop + top
                    extraDistance.setAttribute('bottom', str(-dist))

                for chord in chordStaff.getElementsByTagName('chord'):
                    display = chord.gotoChild('display')
                    display.setAttribute('color','FFFFFF')
                for clefSign in chordStaff.getElementsByTagName('clefSign'):
                    clefSign.setAttribute('color','FFFFFF')
                for keySign in chordStaff.getElementsByTagName('keySign'):
                    keySign.setAttribute('color','FFFFFF')
                for timeSign in chordStaff.getElementsByTagName('timeSign'):
                    timeSign.setAttribute('color','FFFFFF')
                for barline in chordStaff.getElementsByTagName('barline'):
                    barline.setAttribute('color','FFFFFF')
                    

    return True
        

            
class ScoreChange(ScoreChange):

    def changeScore(self, score):
        global scriptAction, doc
        doc = score.parentNode
        changeDoc(score)
        

        
if activeScore():

    activeScore().registerUndo( tr('regUndo') )
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)

    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)


