# -*- coding: ISO-8859-1 -*-
""" capellaScript -- 23.01.2004 Paul Villiger
>>> The Swinger

    Noten werden in terngeren oder punktierten Rythmus umgewandelt und auch wieder zurueckk
    Die Umwandlung erfolgt auf der Basis von 4-tel oder 8-tel Noten.

    Fuer die Umwandlung koennen folgende Bereiche ausgewaehlt werden:
    - Einfacher Cursor -> aktuelle Stimme in der Notenzeile
    - Noten markiert in einer Zeile -> markierte Noten
    - mehrere Notenzeilen markiert -> aktuelles System
    - mehrere Systeme markiert -> markierte Systeme
    - ganze Partitur

<<<
History:  22.01.2004 - Erste Version
          23.01.2004 - Pausen werden beruecksichtigt, Fehler mit staffLayout behoben
"""

import xml.dom
from xml.dom.minidom import NodeList

def getNoteObjects(voice):  # returns a List
    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 makeSwingTernaer(chordA, chordB):
    global doc
    durationA = chordA.getElementsByTagName('duration')[0]
    durationB = chordB.getElementsByTagName('duration')[0]
    baseA = durationA.getAttribute('base')
    baseB = durationB.getAttribute('base')
    # eventuelle Punktierung entfernen
    if durationA.getAttribute('dots') <> '':
        durationA.removeAttribute('dots')
        baseB = baseA
        durationB.setAttribute('base',baseB)
    if durationB.getAttribute('dots') <> '':
        durationB.removeAttribute('dots')

    # Triolenunterteilung entfernen und Notendauer gleich setzen    
    for tuplet in durationA.getElementsByTagName('tuplet'):
        baseA = baseB
        durationA.setAttribute('base',baseA)
        tuplet.removeAttribute('count')
    for tuplet in durationB.getElementsByTagName('tuplet'):
        tuplet.removeAttribute('count')
        
    # erste Note verlaengern  
    if baseA == '1/4':
        baseA = '1/2'
    elif baseA == '1/8':
        baseA = '1/4'
    elif baseA == '1/16':
        baseA = '1/8'
    durationA.setAttribute('base',baseA)
    
    # Noten in Triolen umwandeln
    tuplets = durationA.getElementsByTagName('tuplet')
    if tuplets.length == 0:
        tuplet = doc.createElement('tuplet')
        durationA.appendChild(tuplet)
    else:
        tuplet = tuplets[0]
    tuplet.setAttribute('count','3')
    
    tuplets = durationB.getElementsByTagName('tuplet')
    if tuplets.length == 0:
        tuplet = doc.createElement('tuplet')
        durationB.appendChild(tuplet)
    else:
        tuplet = tuplets[0]
    tuplet.setAttribute('count','3')
    
    # TODO Triolenklammern

def makeSwingDotted(chordA, chordB):
    global doc
    durationA = chordA.getElementsByTagName('duration')[0]
    durationB = chordB.getElementsByTagName('duration')[0]
    baseA = durationA.getAttribute('base')
    baseB = durationB.getAttribute('base')
    # eventuelle Punktierung entfernen
    if durationA.getAttribute('dots') <> '':
        durationA.removeAttribute('dots')
        baseB = baseA
        durationB.setAttribute('base',baseB)
    if durationB.getAttribute('dots') <> '':
        durationB.removeAttribute('dots')

    # Triolenunterteilung entfernen und Notendauer gleich setzen    
    for tuplet in durationA.getElementsByTagName('tuplet'):
        baseA = baseB
        durationA.setAttribute('base',baseA)
        tuplet.removeAttribute('count')
    for tuplet in durationB.getElementsByTagName('tuplet'):
        tuplet.removeAttribute('count')
        
    # erste Note punktieren
    durationA.setAttribute('dots','1')

    # zweite Note verkuerzen
    if baseB == '1/4':
        baseB = '1/8'
    elif baseB == '1/8':
        baseB = '1/16'
    durationB.setAttribute('base',baseB)
    

def makeSwingNoSwing(chordA, chordB):
    global doc
    durationA = chordA.getElementsByTagName('duration')[0]
    durationB = chordB.getElementsByTagName('duration')[0]
    baseA = durationA.getAttribute('base')
    baseB = durationB.getAttribute('base')
    # eventuelle Punktierung entfernen
    if durationA.getAttribute('dots') <> '':
        durationA.removeAttribute('dots')
        baseB = baseA
        durationB.setAttribute('base',baseB)
    if durationB.getAttribute('dots') <> '':
        durationB.removeAttribute('dots')

    # Triolenunterteilung entfernen und Notendauer gleich setzen    
    for tuplet in durationA.getElementsByTagName('tuplet'):
        baseA = baseB
        durationA.setAttribute('base',baseA)
        tuplet.removeAttribute('count')
    for tuplet in durationB.getElementsByTagName('tuplet'):
        tuplet.removeAttribute('count')
        

def getDialogValues(info, info2):

    global swingBase, swingAction, changeScore, changeVoice

    rad1 = Radio(['Ternärer Swing', 'Punktierter Swing', 'Swing entfernen'], value = 0, padding = 8)
    hbox1 = HBox([rad1], text = 'Aktion', width = 30)

    rad2 = Radio(['Swingbasis 1/4', 'Swingbasis 1/8'], value = 0, padding = 8)
    hbox2 = HBox([rad2], text = 'Auswahl Swing Basis', width = 30)
    
    if info2 == '':
        rad3 = Radio([info,'Aenderung in ganzer Partitur'], value = 0, padding = 8)
    else:
        rad3 = Radio([info,'Aenderung in ganzer Partitur', info2], value = 0, padding = 8)

    hbox3 = HBox([rad3], text = 'Auswahl Bereich', width = 30)
    
    vbox1 = VBox([hbox1, hbox2, hbox3])
    
    dlg = Dialog('The Swinger', vbox1)

    if dlg.run():
        swingAction = rad1.value()
        swingBase = [2,4][rad2.value()]
        changeScore = rad3.value() == 1
        changeVoice = rad3.value() == 2
        return True
    else:
        return False
    
def changeDoc(score):
    sel = curSelection()
    sy1, st1, vo1, no1 = sel[0]
    sy2, st2, vo2, no2 = sel[1]
    sy1, sy2 = min(sy1, sy2), max(sy1, sy2)
    st1, st2 = min(st1, st2), max(st1, st2)
    vo1, vo2 = min(vo1, vo2), max(vo1, vo2)
    no1, no2 = min(no1, no2), max(no1, no2)

    info2 = ''
    if sy1 <> sy2:
        wholeSystem = True
        info = 'Aenderung in markierten Systemen'
    elif st1 <> st2:
        wholeSystem = True
        info = 'Aenderung im aktuellen System'
    elif vo1 <> vo2:
        wholeSystem = True
        info = 'Aenderung in aktuellem System'
    elif no1 <> no2:
        wholeSystem = False
        info = 'Aenderung in markierten Noten'
        info2 = 'Aenderung in aktueller Notenzeile, ganze Partitur'
    else:
        wholeSystem = False
        info = 'Aenderung in aktueller Notenzeile'
        info2 = 'Aenderung in aktueller Notenzeile, ganze Partitur'

    if not getDialogValues(info,info2):
        return

    staffLayout = None        

    if changeScore:
        sy1, sy2 = 0, 999
        st1, st2 = 0, 99
        vo1, vo2 = 0, 99
        no1, no2 = 0, 999
    elif changeVoice:
        extSystem = score.getElementsByTagName('system')[sy1]
        extStaff = extSystem.getElementsByTagName('staff')[st1]
        staffLayout = extStaff.getAttribute('layout')
        sy1, sy2 = 0, 999
        st2 = st1 = 999
        vo2 = vo1
        no1, no2 = 0, 999
    elif sy1 <> sy2 or st1 <> st2 or vo1 <> vo2:
        st1, st2 = 0, 99
        vo1, vo2 = 0, 99
        no1, no2 = 0, 999
    elif no1 <> no2:
        pass
    else:
        no1, no2 = 0, 999
        
        
        
    # ?? geht das auch anders, einfacher ??
    possibleSwingBase = [Rational("0"), Rational('1/'+ str(swingBase)), Rational('2/'+ str(swingBase)), Rational('3/'+ str(swingBase))]
    
    for sy in range(activeScore().nSystems()):
        if sy not in range(sy1, sy2 + 1):
            continue
        intSystem = activeScore().system(sy)
        extSystem = score.getElementsByTagName('system')[sy]
        for st in range(intSystem.nStaves()):
            extStaff = extSystem.getElementsByTagName('staff')[st]
            if st in range(st1, st2 + 1) or extStaff.getAttribute('layout') == staffLayout:
                pass
            else:
                continue
            intStaff = intSystem.staff(st)
            for vo in range(intStaff.nVoices()):
                if vo not in range(vo1, vo2 + 1):
                    continue
                intVoice = intStaff.voice(vo)
                extVoice = extStaff.getElementsByTagName('voice')[vo]
                extNoteObjs = getNoteObjects(extVoice)
                wasBarline = True
                for no in range(intVoice.nNoteObjs()-1):
                    intNoteObj = intVoice.noteObj(no)
                    if wasBarline:
                        wasBarline = False
                        durationCount = 0
                    if intNoteObj.implBarline():
                        wasBarline = True
                        continue
                    if intNoteObj.subType() in [NoteObj.EXPL_BARLINE, NoteObj.METER]:
                        wasBarline = True
                        continue
                    if intNoteObj.isChord() or intNoteObj.isRest():
                        pass
                    else:
                        continue
                    intNoteObj1 = intVoice.noteObj(no+1)
                    if intNoteObj1.isChord() or intNoteObj1.isRest():
                        pass
                    else:
                        continue
                    if durationCount in  possibleSwingBase:
                        dur = intNoteObj.duration() + intNoteObj1.duration()
                        if dur.p == 1 and dur.q == swingBase:
                            if no in range(no1, no2):
                                # Wenn ganzes System oder
                                # wenn kein Bereich markiert oder
                                # wenn Cursor innerhalb markiertem Bereich
                                if swingAction == 0:
                                    makeSwingTernaer(extNoteObjs[no],extNoteObjs[no+1])
                                elif swingAction == 1:
                                    makeSwingDotted(extNoteObjs[no],extNoteObjs[no+1])
                                elif swingAction == 2:
                                    makeSwingNoSwing(extNoteObjs[no],extNoteObjs[no+1])

                    # messageBox('duration',str(dur))
                    durationCount = durationCount + intNoteObj.duration() 
                    

# 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("The_Swinger")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)
    
    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)
