# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Grafische Noten

    Anwendung: Cursor vor die Note stellen und Skript aufrufen|
    - Der Notenwert inklusive Haltebogen wird in eine grafische Note umgesetzt
    - Die Halsrichtung wird berechnet und kann manuell angepasst werden
    - Die Lage der Punktierung wird berechnet und kann manuell angepasst werden
    - Die ursprünglichen Noten  werden unsichtbar gemacht
    - Bei Haltebogen am Zeilenende wird der Wert auf der Folgezeile
      nicht berücksichtigt und muss manuell eingegeben werden
    - ungültige Punktierungen müssen manuell korrigiert werden.
    - Die Elemente sind nicht gruppiert und können manuell verschoben werden

    Vorschlagnoten:
    - Die Grösse ist 2/3 der normalen Noten
    - nur einfache Vorschlagnoten sind möglich
    - bei 8tel wird ein Abbreviaturstrich gezeichnet
    - Punktierung wird nicht unterstützt
    - Halsrichtung nach oben oder unten
    
<<<

History:  17.12.03 - Erste Ausgabe
          22.12.03 - Undo aktualisiert
          05.01.04 - Punktierung und Halsrichtung werden automatisch berechnet
          25.01.05 - Grafische Vorschlagnoten
          26.01.05 - Dialog verbessert, neu mit Notenauswahl
          06.02.05 - Fehler bei Halsrichtung
          04.04.05 - bis 4-fach Punktierung
          06.04.05 - Hilfslinien werden gezeichnet
          30.11.06 - Vorschlagnoten nach unten
                     
                    
"""
import xml.dom, new
from xml.dom.minidom import NodeList, Node, Element

doc = [] # parentNode von score
clefOffset = 41

def latin1_e(u):
    return u.encode('Latin-1')
def latin1_d(u):
    return u.decode('Latin-1')

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 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 getCursor():
    sel = curSelection()
    result = None
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    #if sel[0] != sel[1]:
    #    messageBox('Fehler', 'Markierung ist nicht leer')
    #    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 setChordInvisible(chord, tremolo=False):
    if chord.nodeType == chord.ELEMENT_NODE and chord.tagName == 'chord':
        display = addElementNode(chord,'display')
        display.setAttribute('invisible','true')
        if tremolo:
            display.setAttribute('small','true')
            stem = addElementNode(chord,'stem')
            stem.setAttribute('dir','up')
            stem.setAttribute('tremoloBars','1')
            duration = addElementNode(chord,'duration')
            duration.setAttribute('noDuration','true')

def addNoteLines(chord, relPitch, tremolo=False):
    if chord.nodeType == chord.ELEMENT_NODE and chord.tagName == 'chord':
        if relPitch >= 3 or relPitch <= -3:
            drawObjects = chord.gotoChild('drawObjects')
            drawObj = drawObjects.gotoChild('drawObj', True)
            noteLines = drawObj.gotoChild('notelines')
            dy = ((abs(relPitch)  - 2) // 1 ) * relPitch / abs(relPitch)
            messageBox('',str(relPitch))
            noteLines.setAttribute('y',str(dy))
            noteLines.setAttribute('x1','-0.4')
            if tremolo: 
                noteLines.setAttribute('x2','1.2')
            else:
                noteLines.setAttribute('x2','1.6')
            

def hasTie(chord):
    tie = chord.getElementsByTagName('tie')
    if tie.length > 0:
        tie = tie[0]
        if tie.hasAttribute and tie.getAttribute('begin') == 'true':
            return True
    return False

def getDuration(chord):
    duration = chord.getElementsByTagName('duration')
    if duration.length > 0:
        duration = duration[0]
        base = float(eval(duration.getAttribute('base')+'.0'))
        dots = duration.getAttribute('dots')
        if dots == '1':
            base = base * 1.5
        elif dots == '2':
            base = base * 1.75
        return base
    else:
        return 0.0

def findClef(voice, num):
    clef = 'G2'
    noteObject = voice.getElementsByTagName('noteObjects')[0]
    notes = getElementObjects(noteObject.childNodes)
    for n in range(num):
        if notes[n].tagName == 'clefSign':
            clef = notes[n].getAttribute('clef')
    return clef

def calculateClefOffset(c):
    global clefOffset
    # Berechnet den Offset der Note, welche bei diesem Schluessel auf der Mittellinie steht
    # Schlüssel umwandeln
    if c == 'treble':
        newC = 'G2'
    elif c == 'bass':
        newC = 'F4'
    elif c == 'alto':
        newC = 'C3'
    elif c == 'tenor':
        newC = 'C4'
    else:
        newC = c

    # Schlüssel analysieren
    clef = newC[0]
    line = int(newC[1])
    if newC.find('-') > 0:
        okt = -7
    elif newC.find('+') > 0:
        okt = 7
    else:
        okt = 0
    # Offset berechnen
    offset = - 2 * line + okt
    if clef == 'G':  # G-Schlüssel
        offset += 45
    elif clef == 'F':
        offset += 37 # F-Schlüssel
    elif clef == 'C':
        offset += 41 # C-Schlüssel
    elif clef in  ['N','P']:
        offset += 47 # Schlagzeug & Kein Schlüssel
    else:
        offset = clefOffset
    clefOffset = offset

def getPitch(chord):
    notenString='CDEFGAB'
    head = chord.getElementsByTagName('head')[0]
    pitch = head.getAttribute('pitch')
    pitchN = notenString.find(pitch[0]) + 7 * long(pitch[1])
    off = - (pitchN - clefOffset) / 2.0  # B5 auf Mittellinie
    return off



def getNote(duration):
    noteChr = ((226,226,1.00),  # 4/1 char up, char down, dx
               (225,225,1.00),  # 2/1
               (227,227,0.7),  # 1/1
               (160,161,0.25),  # 1/2
               (162,163,0.25),  # 1/4
               (164,165,0.75),  # 1/8
               (166,167,0.75),  # 1/16
               (168,169,0.75),  # 1/32
               (170,171,0.75))  # 1/64
    n = 0
    while duration < 4 and n < 8:
        duration = duration * 2
        n = n + 1
    return noteChr[n]

def getDots(duration):    
    while duration < 4:
        duration = duration * 2
    if duration > 7.7:
        dots = 4
    elif duration > 7.4:
        dots = 3
    elif duration > 6.9:
        dots = 2
    elif duration > 5.9:
        dots = 1
    else:
        dots = 0
    return dots
        
def addDotSymbol(chord, dots, up, dx):
    global doc
    sym = chr(46)
    if up:
        y = '-0.5'
    else:
        y = '0'

    dotCount = dots
    x = dx
    
    if chord.nodeType == chord.ELEMENT_NODE and chord.tagName == 'chord':
        while dotCount > 0:
            drawObjects = addElementNode(chord,'drawObjects')
            drawObj = addNewElementNode(drawObjects,'drawObj')
                
            text = addNewElementNode(drawObj,'text')
            text.setAttribute('x',str(x))
            text.setAttribute('y', y)
                
            font = addNewElementNode(text,'font')
            font.setAttribute('face','capella3')
            font.setAttribute('height','18')
            font.setAttribute('charSet','2')
            font.setAttribute('pitchAndFamily','2')
    
            content = addNewElementNode(text,'content')
            textNode = doc.createTextNode(latin1_d(sym))
            content.appendChild(textNode)            
    
            basic = addElementNode(drawObj,'basic')
            basic.setAttribute('vertAlign','1')

            dotCount -= 1
            x += 0.5
            
def addCapSymbol(chord,sym,small=False):
    global doc
    if chord.nodeType == chord.ELEMENT_NODE and chord.tagName == 'chord':
        drawObjects = addElementNode(chord,'drawObjects')
        drawObj = addNewElementNode(drawObjects,'drawObj')
            
        text = addNewElementNode(drawObj,'text')
        text.setAttribute('x','0')
        text.setAttribute('y','0')
            
        font = addNewElementNode(text,'font')
        font.setAttribute('face','capella3')
        if small:
            font.setAttribute('height','12')
        else:
            font.setAttribute('height','18')
            
        font.setAttribute('charSet','2')
        font.setAttribute('pitchAndFamily','2')

        content = addNewElementNode(text,'content')
        textNode = doc.createTextNode(latin1_d(sym))
        content.appendChild(textNode)            

        basic = addElementNode(drawObj,'basic')
        basic.setAttribute('vertAlign','1')

        if small and ord(sym) == 164:  # klein und 'Achtel nach oben'
            drawObj = addNewElementNode(drawObjects,'drawObj')
            line = addNewElementNode(drawObj,'line')
            line.setAttribute('x1','0.3125')
            line.setAttribute('y1','-1.125')
            line.setAttribute('x2','1.53125')
            line.setAttribute('y2','-1.78125')
            basic = addElementNode(drawObj,'basic')
            basic.setAttribute('vertAlign','1')
            
        if small and ord(sym) == 165:  # klein und 'Achtel nach unten'
            drawObj = addNewElementNode(drawObjects,'drawObj')
            line = addNewElementNode(drawObj,'line')
            line.setAttribute('x1','-0.4375')
            line.setAttribute('y1','1.125')
            line.setAttribute('x2','0.78125')
            line.setAttribute('y2','1.78125')
            basic = addElementNode(drawObj,'basic')
            basic.setAttribute('vertAlign','1')
                

def getDialog(dur, relPitch):
    # Input: Duration, Abstand von Mittellinie

    duration = dur
    n = 0
    while duration < 4 and n < 8:
        duration = duration * 2
        n = n + 1

    duration = dur
    while duration < 4:
        duration = duration * 2
    if   duration > 7.7:    dots = 4
    elif duration > 7.4:    dots = 3
    elif duration > 6.9:    dots = 2
    elif duration > 5.9:    dots = 1
    else:                   dots = 0

    hals = relPitch > 0
    dot = (relPitch * 2) % 2 < 1  # modulo 2
    
    edit1 = ComboBox(['4/1','2/1','1/1','1/2','1/4','1/8','1/16','1/32','1/64'],value=n,width=10, padding = 8)
    edit2 = ComboBox(['0','1','2','3','4'], value=dots, width = 10 )
    chk2 = CheckBox('Halsrichtung oben ', value = hals, padding = 8)

    chk3 = CheckBox('Punktierung oben ', value = dot, padding = 8)

    chk4 = CheckBox('Vorschlagnote ', value = False, padding = 8)
    chk5 = CheckBox('    nach unten ', value = False, padding = 8)

    hbox1 = HBox([Label('Notenlaenge:', width=10), edit1,Label('Punktierung:', width=10), edit2], padding = 8 )

    vbox = VBox([hbox1, chk2, chk3, chk4, chk5, Label('')], padding=8)

    dlg = Dialog('-- Grafische Noten --', vbox)

    if dlg.run():
        
        
        newDur = [4.0, 2.0, 1.0, 1.0/2, 1.0/4, 1.0/8, 1.0/16, 1.0/32, 1.0/64][edit1.value()] 
        newDur = newDur * [1.0, 1.5, 1.75, 1.875, 1.9375][edit2.value()]
        dotUp = chk3.value() == 1
        tremolo = chk4.value() == 1
        tremoloDown = chk5.value() == 1
        if tremolo:
            if tremoloDown:
                stemUp = False
            else:
                stemUp = True
        else:
            stemUp = chk2.value() == 1
        
        return (newDur, stemUp, dotUp, tremolo)
    else:
        return (dur, False, False, False)


def changeDoc(score):
    sel = getCursor()
    if sel == None:
        #
        return
    else:
        system = score.getElementsByTagName('system')[sel[0]]
        staff = system.getElementsByTagName('staff')[sel[1]]
        voice = staff.getElementsByTagName('voice')[sel[2]]
        noteObject = voice.getElementsByTagName('noteObjects')[0]
        objList = getElementObjects(noteObject.childNodes)
        if objList.length <= sel[3]:
            return

        
        obj = objList[sel[3]]
        if obj.tagName == 'chord':
            
            clef = findClef(voice, sel[3])
            clefOffset = calculateClefOffset(clef)
            relPitch = getPitch(obj)

            dur = getDuration(obj)
            l = objList.length
            n = sel[3]
            while  n+1 < l and hasTie(objList[n]):
                n = n+1
                dur = dur + getDuration(objList[n])
                setChordInvisible(objList[n])
            
            dur, stemUp, dotUp , tremolo= getDialog(dur, relPitch)
            
            noteSym = getNote(dur)
            if stemUp:
                ch = noteSym[0]
            else:
                ch = noteSym[1]
                
            dx = noteSym[2]
            dot = getDots(dur)

            addCapSymbol(obj,chr(ch), tremolo)
            addDotSymbol(obj,dot, dotUp, dx)
            setChordInvisible(obj, tremolo)
            addNoteLines(obj, relPitch, tremolo)
            
        else:
            pass
       

# 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("Grafische Noten")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)
    
    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)

