# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Akkordbegleitung

    Dieses Skript teilt eine Notenzeile mit Akkordnoten in einzelne Noten auf.

    Rückmeldungen bitte an villpaul(a)bluewin.ch
<<<

History: 02.11.2008 - Erste Ausgabe

"""
german = ("de", {
    'dialogHeader'      :   ' Akkordbegleitung ',
    'regUndo'           :   'Akkordbegleitung',
    'headSelect'        :   'Kopfauswahl:',
    'durationSelect'    :   'Notenwert:',
    'layoutSelect'      :   'Notenzeile:'
    } )

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 parseString, NodeList, Node, Element
from caplib.rational import Rational
from caplib.capDOM import ScoreChange
import tempfile, string, new

def latin1(u):
    return u.encode('Latin-1')

doc = parseString('<score/>')

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 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 cmpHeads(x,y):
    px = x.getAttribute('pitch')
    py = y.getAttribute('pitch')
    ax = 0
    ay = 0
    for alter in x.getElementsByTagName('alter'):
        ax = int(alter.getAttribute('step'))
    for alter in y.getElementsByTagName('alter'):
        ay = int(alter.getAttribute('step'))
    if px == py:
        return cmp(ax, ay)
    if cmp(px[1], py[1]) <> 0:
        return cmp(px[1], py[1])
    ix = string.find('CDEFGAB', px[0])
    iy = string.find('CDEFGAB', py[0])
    return cmp(ix, iy)
    

def convertChordLine(staff, headSelect, durationSelect):
    combinedSelect = []
    for i in range(len(headSelect)):
        combinedSelect.append( (headSelect[i], durationSelect[i]) )
        
    for voice in staff.getElementsByTagName('voice'):
        for chord in voice.getElementsByTagName('chord'):
            chordDuration = getDuration(chord)
            headList = []
            for head in chord.getElementsByTagName('head'):
                headList.append(head)
            headList.sort(cmpHeads)

            while chordDuration > 0:
                for h,d in combinedSelect:
                    tupletFactor = '1'
                    dotFactor = '1'
                    for c in d:
                        if c.lower() == 't':
                            tupletFactor = '2/3'
                        elif c == '.':
                            dotFactor = '3/2'
                    d = string.replace(d,'t','')
                    d = string.replace(d,'T','')
                    d = string.replace(d,'.','')

                    if d[0] in '1248':
                        dur = '1/' + d[0]
                    elif d[0] =='6':
                        dur = '1/16'
                    else:
                        continue
                    if h == 'p':
                        newChord = doc.createElement('rest')
                        duration = newChord.gotoChild('duration')
                        duration.setAttribute('base',dur)
                    else:
                        newChord = doc.createElement('chord')
                        duration = newChord.gotoChild('duration')
                        duration.setAttribute('base',dur)
                        heads = newChord.gotoChild('heads')
                        for s in h:
                            i = int(s) -1
                            if i >= len(headList):
                                i = len(headList) - 1
                            newHead = headList[i].cloneNode(True)
                            heads.appendChild(newHead)
                    if dotFactor <> '1':
                        duration.setAttribute('dots', '1')
                    if tupletFactor <> '1':
                        tuplet = duration.gotoChild('tuplet')
                        tuplet.setAttribute('count', '3')
                    
                    chord.parentNode.insertBefore(newChord, chord)

                    chordDuration -= Rational(dur) * Rational(tupletFactor) * Rational(dotFactor)

                    if chordDuration <= 0:
                        break
                    
            chord.parentNode.removeChild(chord)


def getAllLayouts(score):
    # Gibt eine Liste zurueck mit allen Eintraegen aus dem Mustersystem,
    staffLayoutList = []
    for staffLayout in score.getElementsByTagName('staffLayout'):
        staffLayoutList.append(latin1(staffLayout.getAttribute('description')))

    return staffLayoutList
            

options = ScriptOptions() 
opt = options.get()

def userInput(staffLayoutList):
    i = 0
    if 'Akkordzeile' in staffLayoutList:
        for sl in staffLayoutList:
            if sl == 'Akkordzeile':
                break
            i += 1

    layoutSelect = ComboBox(staffLayoutList, value = i, width = 20)
    headSelect = Edit(value = opt.setdefault('headSelect','1234'), width = 20)
    durationSelect = Edit(value = opt.setdefault('durationSelect','4'), width = 20)
    dlg = Dialog(tr('dialogHeader'),
                 VBox([HBox([Label(tr('layoutSelect'), width = 10),layoutSelect]),
                       Label(' '),
                       HBox([Label(tr('headSelect'), width = 10),headSelect]),
                       HBox([Label(' ', width = 10), Label('Zahl=Akkordnote, p=Pause')]),
                       HBox([Label(' ', width = 10), Label('z.B. 1 2 3 od. 1234 od, p 1 2 3')]),
                       Label(' '),
                       HBox([Label(tr('durationSelect'), width = 10),durationSelect]),
                       HBox([Label(' ', width = 10), Label('1=1/1, 2=1/2, ..., 6=1/16')]),
                       HBox([Label(' ', width = 10), Label('.=Punktierung, t=Triole')]),
                       HBox([Label(' ', width = 10), Label('z.B. 4 8 8 od. 4. 8 od 8t 8t 8t')]),
                       Label(' ')
                       ]))
    if dlg.run():
        ok = True
        durationSelect = durationSelect.value().strip()
        headSelect = headSelect.value().strip()
        
        opt['durationSelect'] = durationSelect
        opt['headSelect'] = headSelect
        options.set(opt)

        durationSelect = durationSelect.split()
        headSelect = headSelect.split()
        layoutSelect = staffLayoutList[layoutSelect.value()]
    else:
        ok = False
        durationSelect = []
        headSelect = []
        layoutSelect = ''

    if len(durationSelect) <> len(headSelect):
        ok = False
    elif len(durationSelect) == 0:
        ok = False
    elif len(headSelect) == 0:
        ok = False

    return ok, headSelect, durationSelect, layoutSelect

handlingOk = True

def changeDoc(score):
    staffLayoutList = getAllLayouts(score)
    
    ok, selectedHeads, selectedDurations, selectedLayout = userInput(staffLayoutList)
    if not ok:
        handlingOk = False
        return
    
    for staff in score.getElementsByTagName('staff'):
        if selectedLayout == staff.getAttribute('layout'):
            convertChordLine(staff, selectedHeads, selectedDurations)

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)

    if handlingOk:
        activeScore().read(tempOutput)
        
    os.remove(tempInput)
    os.remove(tempOutput)


