# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger, Hartmut Lemmel
>>> Balkenteilung

    Mit diesem Skript können:
    - beliebige Balkenteilungen auf 8-tel Basis eingegeben werden
    - Balkengruppen aus kleinen Notenwerten stärker unterteilt werden
    - alle Balken zwischen kleinen und großen Noten unterbrochen werden
    Vorgehen:
    - Alles markieren und unter Format - Systeme - Allgemein die Einstellung auf Ganztaktbalken setzen
    - Alles markieren und unter Format - Balken Einstellung auf automatisch setzen
    - Skript aufrufen und Balkenteilung eingeben.

    Rückmeldungen bitte an mailto:villpaul@swissonline.ch

<<<

History: 26.09.04 - Erster Entwurf

        16.6.05, Hartmut Lemmel:
          Neue Funktionen: 
          * Nur Balken teilen, die Noten enthalten, deren Wert kleiner als eine Vorgabe ist
          * Balken zwischen großen und kleinen Noten teilen
          Verbesserungen:
          * Dialogeinstellungen werden gespeichert
          * Dialog kann mit Esc abgebrochen werden, ohne dass das weitere Skript abläuft
          * Noten ohne Wert wurden bisher bei der Berechnung der Splitpositionen nicht berücksichigt  

        15.03.2008 Berücksichtigung von 3-fach Punktierung

"""

from xml.dom.minidom import NodeList, Node, Element
from caplib.rational import Rational
from caplib.capDOM import ScoreChange
import tempfile, string, new

# 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 calcBeamCutList(bc):
    beamCutList = []
    dur = Rational('0')
    for el in string.split(bc,'+'):
        if '0' < el <='9':
            dur = dur + Rational(str(el + '/8'))                        
            beamCutList.append(dur)
    return beamCutList
            
    
def isvisible(chord):
    if chord.getElementsByTagName('display').length != 0:
        dis = chord.getElementsByTagName('display')[0]
        if dis.getAttribute('invisible') == 'true':
            return 0
    return 1

def getSize(chord):
    if chord.getElementsByTagName('display').length != 0:
        dis = chord.getElementsByTagName('display')[0]
        if dis.getAttribute('small') == 'true':
            return 1
    return 0

def getBaseDuration(chord):
    if chord.getElementsByTagName('duration').length == 0:
        return Rational('0')
    dur = chord.getElementsByTagName('duration')[0]
    return(Rational(str(dur.getAttribute('base'))))

def getTime(noteObj, attr):
    timestr=str(noteObj.getAttribute(attr))
    if timestr=='C':
        timestr='4/4'
    elif timestr=='allaBreve':
        timestr='4/4'
    return(Rational(timestr))

def getDuration(chord):
    if chord.getElementsByTagName('duration').length == 0:
        return Rational('0')
    dur = chord.getElementsByTagName('duration')[0]
    if dur.getAttribute('noDuration') == 'true':
        return Rational('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
        
    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 changeDoc(score):
    for staff in score.getElementsByTagName('staff'):
        for noteObjects in staff.getElementsByTagName('noteObjects'):
            actualDuration = measureDuration = getTime(staff,'defaultTime')
            oldnotevalid=0
            for noteObj in noteObjects.getChildElements():
                if actualDuration >= measureDuration:                    
                    actualDuration = Rational('0')
                    smallvalue=0
                    oldsplitposvalid=0
                    oldnotevalid=0
                if noteObj.nodeName == 'timeSign':
                    actualDuration = measureDuration = getTime(noteObj,'time')
                elif noteObj.nodeName in ['barline', 'keySign']:
                    actualDuration = measureDuration
                elif noteObj.nodeName == 'rest':
                    actualDuration += getDuration(noteObj)
                    oldnotevalid=0
                elif noteObj.nodeName == 'chord':
                    if splitsize:
                        if getBaseDuration(noteObj) <= Rational('1/8') and isvisible(noteObj):
                            notesize = getSize(noteObj)
                            if oldnotevalid:
                                if oldnotesize!=notesize:
                                    oldnote.setAttribute('group','split')
                            oldnotesize=notesize
                            oldnote=noteObj.gotoChild('beam')
                            oldnotevalid=1
                        else:
                            oldnotevalid=0
                    noteDuration = getDuration(noteObj)                            
                    if noteDuration > 0:
                        if noteDuration<=maxValue:
                            smallvalue=1
                            if oldsplitposvalid:
                                oldsplitpos.setAttribute('group','split')
                                oldsplitposvalid=0
                        actualDuration += noteDuration
                        if actualDuration in beamCutList:
                          if actualDuration < measureDuration:
                            if getBaseDuration(noteObj) <= Rational('1/8'):
                                beam = noteObj.gotoChild('beam')
                                if smallvalue: # links von der Trennungsstelle waren bereits kleine Notenwerte
                                    beam.setAttribute('group','split')
                                else: # Balken für später merken, falls rechts von der Trennungsstelle noch kleine Notenwerte kommen
                                    oldsplitpos=beam
                                    oldsplitposvalid=1
                            smallvalue=0
                                


class ScoreChange(ScoreChange):

    def changeScore(self, score):
        global scriptAction, doc
        doc = score.parentNode
        changeDoc(score)
        
if activeScore():
 options = ScriptOptions() 
 opt = options.get()  
 dlgEditTeilung = Edit(opt.get('Teilung','2+2'), width = 32, padding = 8)
 dlgEditMaxWert = Edit(opt.get('MaxWert','8'), width = 3, padding = 8)
 chxSplitSize = CheckBox('Balken zwischen kleinen und großen Noten immer teilen',value=int(opt.get('SplitSize','1')))
 dlg = Dialog('Balkenteilung',VBox([Label('Bitte Balkenteilung eingeben.'),
                                   Label('(Beispiel 3+2+2)'),
                                   dlgEditTeilung,
                                   Label('Balken nur teilen wenn Notenwerte kleiner gleich'),
                                   HBox([Label('1/'),dlgEditMaxWert,Label(' enthalten sind (1/8 = immer teilen)')]),
                                   chxSplitSize
                                   ], padding = 8))
 if dlg.run():
    splitsize=chxSplitSize.value();
    opt.update(dict(Teilung=dlgEditTeilung.value(), MaxWert=dlgEditMaxWert.value(), SplitSize=splitsize))
    options.set(opt)
     
    beamCutList = calcBeamCutList(dlgEditTeilung.value())
    maxValue = Rational('1/'+dlgEditMaxWert.value())
    activeScore().registerUndo("Balkenteilung")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)

    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)

