# -*- coding: ISO-8859-1 -*-
""" capellaScript -- 01.12.2005 Andreas Herzog
>>> VoiceCocktail

    Das Skript macht aus mehreren Stimmen eine einzige.|
    |
    Einschränkung: Bei bestimmten unregelmäßigen Teilung (Triolen, etc.) kann es sein, dass das Skript die Noten nicht mehr richtig zusammensetzen kann.
    |
    Version 1.0d   |
   
    |
    
        |

<<<


"""
# Version 1.0: Ursprungsversion
# Version 1.0a: fehlerkorrigierter Dialog
#         1.0b: 15.03.08 Behandlung von 3-fach Punktierung (Vlg)
#         1.0c: 16.10.08 Loop korrigiert (Vlg)
#         1.0d  01.08.10 Script neu formatiert, doppelte Zeilenumbrüche gelöscht (Vlg)


import xml.dom
import string, new


from xml.dom.minidom import NodeList, Node, Element


# doc = [] # parentNode von score

def latin1_e(u):
    return u.encode('Latin-1')
def latin1_d(u):
    return u.decode('Latin-1')
    
    
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

    
def Test(text):
    messageBox('Test',str(text))
            
         
def getCursor():
    sel = curSelection()
    result = None
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    result = sel[0]
    return result

def getElementObjects(objList):  # returns a List
    newList = NodeList()
    for n in range(objList.length):
        if objList[n].nodeType == objList[n].ELEMENT_NODE:
            newList.append(objList[n])
    return newList

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 getTime(chord):			
    objList = getElementObjects(chord.parentNode.parentNode.getElementsByTagName('noteObjects')[0].childNodes)
    time = 0
    i = 0
    while chord <> objList[i]:
        objects = objList[i]
        addTime = getLength(objects)
        time +=  addTime
        i += 1
    return time


def getLength(object):
    length = 0
    if object.tagName in ['chord','rest']:
        for duration in object.getElementsByTagName('duration'):
            if duration.getAttribute('noDuration') <> 'true':
                if not duration.getAttribute('base') in ['1']:
                    length = 1 / string.atof(duration.getAttribute('base')[2:])

                else:
                    length = 1
                dotsMult = 1
                if duration.getAttribute('dots') == '1':
                    dotsMult = 1.5
                elif duration.getAttribute('dots') == '2':
                    dotsMult = 1.75					
                elif duration.getAttribute('dots') == '3':
                    dotsMult = 1.875
                length = length * dotsMult
    return length
    
def fillUpWithRest(newObject, length0, length1):
    
    differenzNeu = length0-length1

    i = 1
    while differenzNeu > 0 and i < 128:
        a = 0
        #Test(str(i) + ',' + str((1/float(i))*1.50)+','+str(differenzNeu))
        if ((1/float(i))*1.75) <= differenzNeu:
             #Test(str(i) + ',' + str((1/i)*1.75)+','+str(differenzNeu))
             newRest = addNewElementNode(newObject.parentNode, 'rest')
             newRestDuration = addNewElementNode(newRest, 'duration')
             newObject.parentNode.insertBefore(newRest, newObject.nextSibling)
             newRestDuration.setAttribute('base', '1/'+str(i))
             newRestDuration.setAttribute('dots','2')
             a = (1/float(i))*1.75
             
        else:
            if (1/float(i))*1.50 <= differenzNeu:
                newRest = addNewElementNode(newObject.parentNode, 'rest')
                newRestDuration = addNewElementNode(newRest, 'duration')
                newObject.parentNode.insertBefore(newRest, newObject.nextSibling)
                newRestDuration.setAttribute('base', '1/'+str(i))
                newRestDuration.setAttribute('dots','1')
                a = (1/float(i))*1.50
            else:
                if (1/float(i)) <= differenzNeu:
                    newRest = addNewElementNode(newObject.parentNode, 'rest')
                    newRestDuration = addNewElementNode(newRest, 'duration')
                    newObject.parentNode.insertBefore(newRest, newObject.nextSibling)
                    newRestDuration.setAttribute('base', '1/'+str(i))
                    newRestDuration.setAttribute('dots','')	
                    a = (1/float(i))
                
        differenzNeu = differenzNeu - a
        i*=2	

def testSmall(object):
    headSmall = False
    for display in object.getElementsByTagName('display'):		# Ignorieren der kurzen Vorschlaege
        headSmall = display.getAttribute('small')
        
    return headSmall
        
def testTremoloBars(object):
    tremoloBars = False
    for stem in object.getElementsByTagName('stem'):		# Ignorieren der kurzen Vorschlaege
        if stem.getAttribute('tremoloBars') == '1':
            tremoloBars = True
            #Test('Hallo')
            
    return tremoloBars
    
def testVorschlag(object):
    testVorschlag = False
    if testSmall(object) and testTremoloBars(object):
        testVorschlag = True
        
    return testVorschlag
                
def appendChords(voiceSel, object,i):
    voices = voiceSel.parentNode
    i=0
    for voice in voices.getElementsByTagName('voice'):
        if voice == voiceSel: 
            voiceCountSel = i
        i+= 1	
               
    time0 = getTime(object)
    length0 = getLength(object)
    #Test(length0)
    j= 0
    voiceCount = 0
    for voice in voices.getElementsByTagName('voice'):

        if voiceCount <> voiceCountSel:
            abbruch = 0
            noteObject = voice.getElementsByTagName('noteObjects')[0]
            objList = getElementObjects(noteObject.childNodes)
            k = 0 
            #Test('h')
            while k < len(objList) and abbruch == 0:
                element = objList[k]
                
                    
                if element.tagName in ['chord','rest']:
                    time1 = getTime(element)
                    length1 = getLength(element)

                    if time1 > time0:
                        abbruch = 1
                if element.tagName in ['rest']:                                         # Uebernehmen von kuerzeren Pausen
                    newDuration = element.getElementsByTagName('duration')[0].cloneNode(True)
                    if time1 == time0:
                        if length1 < length0:
                                oldDuration  = object.getElementsByTagName('duration')[0]
                                newDuration = element.getElementsByTagName('duration')[0].cloneNode(True)
                                object.insertBefore(newDuration, oldDuration)
                                object.removeChild(oldDuration)
                                fillUpWithRest(object, length0, length1)
                                
                                length1 = getLength(object)

                if element.tagName in ['chord'] and ((testVorschlag(object) and testVorschlag(element)) or (not testVorschlag(object) and not testVorschlag(element))):
                    newDuration = element.getElementsByTagName('duration')[0].cloneNode(True)
                    if time1 == time0:
                        #Test('Element:' + str(length1) + 'Chord' + str(length0))
                        if length1 > length0:
                            
                            
                            oldDuration  = element.getElementsByTagName('duration')[0]
                            newDuration = object.getElementsByTagName('duration')[0].cloneNode(True)
                            element.insertBefore(newDuration, oldDuration)
                            element.removeChild(oldDuration)
                            fillUpWithRest(element, length1, length0)
                            length1 = getLength(element)
                
                if element.tagName in ['chord'] and ((testVorschlag(object) and testVorschlag(element)) or (not testVorschlag(object) and not testVorschlag(element))):
                    
                    newDuration = element.getElementsByTagName('duration')[0].cloneNode(True)

                    if time1 == time0:
                        
                        if object.tagName == 'chord' and element.tagName == 'chord':
                            heads = object.gotoChild('heads')     # Node heads
                            objectVorschlag = testVorschlag(object)
                             
                            for head in element.getElementsByTagName('head'):  
                                clone = head.cloneNode(True)
                                kopfGefunden = 0
                                for head in object.getElementsByTagName('head'):
                                    if clone.getAttribute('pitch') == head.getAttribute('pitch'):
                                        kopfGefunden = 1
                                if kopfGefunden == 0:	
                                    heads.appendChild(clone)
                                newObject = object
                        

                                                   
                        if object.tagName == 'rest':
                            elementClone = element.cloneNode(True)
                            
                            if length1 > length0:
                                 oldDuration  = elementClone.getElementsByTagName('duration')[0]
                                 newDuration = object.getElementsByTagName('duration')[0].cloneNode(True)
                                 elementClone.insertBefore(newDuration, oldDuration)  
                                 elementClone.removeChild(oldDuration)                  		
                            object.parentNode.appendChild(elementClone)
                            object.parentNode.insertBefore(elementClone,object)
                            object.parentNode.removeChild(object)
                            newObject= elementClone
                            if length1 > length0:
                                oldDuration  = newObject.getElementsByTagName('duration')[0]
                        
                        if length1 < length0:
                            if element.tagName == 'chord':
                                oldDuration  = newObject.getElementsByTagName('duration')[0]
                                newObject.insertBefore(newDuration, oldDuration)
                                newObject.removeChild(oldDuration)
                            
                                fillUpWithRest(newObject, length0, length1)
                                length0 = getLength(newObject)



                k+=1
        voiceCount+=1

def mixVoices(staff):
    voiceCount = 0
    voices = staff.getElementsByTagName('voices')[0]
    voiceSel = voices.getElementsByTagName('voice')[0]
    previousVoiceTime = 0
    for voice in voices.getElementsByTagName('voice'):					# Suchen nach der laengsten Stimme
        noteObjects = voice.getElementsByTagName('noteObjects')[0]
        objects = getElementObjects(noteObjects.childNodes)
        voiceTime = getTime(objects[len(objects)-1])
        if voiceTime > previousVoiceTime:
            voiceSel = voice
            previousVoiceTime = voiceTime
        
    noteObject = voiceSel.getElementsByTagName('noteObjects')[0]
    objList = getElementObjects(noteObject.childNodes)
    i= 0
    while i < len(objList):												# Anhaengen der unteren Stimmen an die oberste Stimme
        element = objList[i]
        if element.tagName in ['chord', 'rest']:
            appendChords(voiceSel, element,i)
            objList = getElementObjects(noteObject.childNodes)
        i+=1
    
    objList = getElementObjects(noteObject.childNodes)	
    
    for voice in voices.getElementsByTagName('voice'):
        if voice <> voiceSel:
            for chord in voice.getElementsByTagName('chord'):
                time = getTime(chord)
                if testVorschlag(chord):
                    for element in objList:
                        if getTime(element) == time:
                    
                            elementClone = chord.cloneNode(True)
                            elementCloneBeam = elementClone.gotoChild('beam')
                            elementCloneBeam.setAttribute('group','split')
                            element.parentNode.appendChild(elementClone)
                            element.parentNode.insertBefore(elementClone, element)
                        

    for voice in voices.getElementsByTagName('voice'):
        if voice <> voiceSel:
            for chord in voice.getElementsByTagName('chord'):
                time = getTime(chord)

                
                for element in objList:
                    timeFirstLine = getTime(element)
                    if timeFirstLine == time:
                        elementFirstLine = element
            
                for drawObjects in chord.getElementsByTagName('drawObjects'):			# Konservieren der Grafikobjekte
                    drawObjList =  getElementObjects(drawObjects.childNodes)
                    for drawObjs in drawObjList:
                        cloneDrawObj = drawObjs.cloneNode(True)
                        drawObjectsFirstLine = elementFirstLine.gotoChild('drawObjects')
                        drawObjectsFirstLine.appendChild(cloneDrawObj)
        
            voices.removeChild(voice)

def changeStem(staff, Richtung):
    for voice in staff.getElementsByTagName('voice'):
        if Richtung == 0:
            voice.setAttribute('stemDir','up')
        if Richtung == 1:
            voice.setAttribute('stemDir','')	
        if Richtung == 2:
            voice.setAttribute('stemDir','down')

def getDialogValues():

    global Auswahl, Richtung
    
    AuswahlRadio = Radio(['nur Zeile mit Cursor','Zeile mit Cursor in allen Systemen','alle Zeilen'],text='Bearbeiten:', value=0)
    RichtungRadio = Radio(['Oberstimme','Hauptstimme','Unterstimme','unverändert'],text='Halsrichtung:', value=3)
    hbox1 = HBox([AuswahlRadio], padding= 4)
    hbox2 = HBox([RichtungRadio], padding= 4)
    
    vbox  = VBox([hbox1, hbox2], text='', padding=8)
    dlg = Dialog('Stimmen zusammenfügen: ', vbox)

    if dlg.run():
        Auswahl = AuswahlRadio.value()
        Richtung = RichtungRadio.value()

        return True
    else:
        return False		

def changeDoc(score):
    global Auswahl, Richtung

    
    sel = getCursor()
    if sel == None:
        #
        return
    else:
        if getDialogValues():
            systemSel = score.getElementsByTagName('system')[sel[0]]
            staffSel = systemSel.getElementsByTagName('staff')[sel[1]]
            staffSelLayout = staffSel.getAttribute('layout')
            voiceSel = staffSel.getElementsByTagName('voice')[sel[2]]
            if Auswahl == 0:
                mixVoices(staffSel)
                changeStem(staffSel, Richtung)
        
            if Auswahl == 1 or Auswahl == 2:
                for staff in score.getElementsByTagName('staff'):
                    if staff.getAttribute('layout') == staffSelLayout or Auswahl == 2:
                        mixVoices(staff)
                        changeStem(staff, Richtung)		
        

        
# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class ScoreChange(ScoreChange):
    
    def changeScore(self, score):
       global doc
       doc = score.parentNode  
       changeDoc(score)
        
        

if activeScore():

    activeScore().registerUndo("Stimmen zusammenfügen")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput, xml=1)
    ScoreChange(tempInput, tempOutput)
    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)
    
