# -*- coding: ISO-8859-1 -*-
""" capellaScript -- Voice_Import / Stimme importieren
>>>
    Dieses Skript erlaubt eine weitere Stimme zu einer Partitur hinzuzufügen.||

    Aus einer zweiten Partitur lässt sich eine Stimme auswählen welche als neue Notenzeile in der aktuellen Partitur
    angehängt wird. Überzählige Noten werden an die letzte Notenzeile gehängt. Das Mustersystem wird erweitert.
    Teilweise fehlende Notenzeilen werden durch Pausen aufgefüllt.
    Das Skript ist gedacht um mehrere Einzelstimmen zu einer Partitur zusammenzufügen.||

    Hinweis: Ist die zu importierende Partitur capella geöffnet so wird sie ohne Rückfrage geschlossen.
    Aus der aktiven Partitur kann nicht importiert werden.

    
<<<

History:  26.03.05 - Betaversion
          28.03.05 - Tonart, Ganztaktpausen, Note am Schluss korrigiert
                   - Textfelder und Grafiken werden aus Import entfernt
                   - Vorschlagnoten werden richtig berücksichtigt
                   - Mehrtaktpausen expandieren
          31.03.05 - Option "Mehrtaktpausen expandieren" ist per default gesetzt
                   - Fehler bei fehlenden Stimmen
          30.04.05 - Kein Import aus aktiver Partitur
          06.01.07 - Tuplets gehen verloren
          15.03.08   Berücksichtigung von 3-fach Punktierung
          04.02.11 - Skriptfehler behoben
          
Rückmeldungen bitte an villpaul(a)bluewin.ch

"""

import new, os.path
from xml.dom.minidom import NodeList, Node, Element
from caplib.rational import Rational
# import win32traceutil

def latin1(u):
    return u.encode('Latin-1')

class voiceCollector(Node):

    def __init__(self):
        self.chordList = []
        self.chordPointer = 0
        
        self.actMeter = '4/4'             # default
        self.actClef ='treble'            # default
        self.actKey = '0'
        self.staffLayout = ''             # zu übertragendes Layout
        self.voiceNumber = 0              # zu übertragende Stimme
        self.lyricsSettings = None
        self.voiceFound = False
        self.expandRest = True

    def collectVoice(self, score):
        for system in score.getElementsByTagName('system'):
            found = False
            for staff in system.getElementsByTagName('staff'):
                if self.staffLayout == staff.getAttribute('layout'):
                    voiceList = staff.getElementsByTagName('voice')
                    if len(voiceList) > self.voiceNumber:
                        found = True
                        for no in getNoteObjects(voiceList[self.voiceNumber]):
                            if no.tagName == 'rest' and self.expandRest:
                                duration = no.gotoChild('duration')
                                if duration.hasAttribute('base') and Rational(str(duration.getAttribute('base'))) > 1:
                                    if duration.hasAttribute('noDuration') and duration.getAttribute('noDuration') == 'true':
                                        self.chordList.append(no)
                                    else:
                                        # Mehrtaktpausen expandieren
                                        dur = Rational(str(duration.getAttribute('base')))
                                        while dur >= Rational(1):
                                            dur -= Rational(1)
                                            newRest = score.parentNode.createElement('rest')
                                            duration = newRest.gotoChild('duration')
                                            duration.setAttribute('base','1')
                                            display = newRest.gotoChild('display')
                                            display.setAttribute('churchStyle','true')
                                            self.chordList.append(newRest)
                                else:
                                    self.chordList.append(no)
                            else:
                                self.chordList.append(no)
                                
                        if self.lyricsSettings == None:
                            self.lyricsSettings = voiceList[self.voiceNumber].gotoChild('lyricsSettings').cloneNode(True)
                            self.voiceFound = True
                        break

            if not found:
                dur = getSystemDuration(system)
                actMeter = restDur = Rational(self.actMeter)
                while dur > 0 and restDur.q < 256:
                    if dur >= actMeter:
                        newRest = score.parentNode.createElement('rest')
                        duration = newRest.gotoChild('duration')
                        duration.setAttribute('base','1')
                        display = newRest.gotoChild('display')
                        display.setAttribute('churchStyle','true')
                        self.chordList.append(newRest)
                        dur = dur - actMeter
                    elif dur >= restDur:
                        newRest = score.parentNode.createElement('rest')
                        duration = newRest.gotoChild('duration')
                        duration.setAttribute('base',str(restDur))
                        self.chordList.append(newRest)
                        dur = dur - restDur
                    else:
                        if restDur > Rational('1/2'):
                            restDur = Rational('1/2')
                        elif restDur > Rational('1/4'):
                            restDur = Rational('1/4')
                        else:
                            restDur = restDur / 2

    def getLayout(self,score):
        layout = score.gotoChild('layout')
        staves = layout.gotoChild('staves')
        staffLayouts = []
        staffLayoutsComboBox = []  # Für CombBox müssen Umlaute "encodiert" werden
        for staffLayout in staves.getElementsByTagName('staffLayout'):
            staffLayouts.append(staffLayout.getAttribute('description'))
            staffLayoutsComboBox.append(latin1(staffLayout.getAttribute('description')))
        layoutSelect = ComboBox(staffLayoutsComboBox, value = 0, width = 20)
        voiceSelect = Edit('1', width = 10)
        expandRest = CheckBox('', value = self.expandRest)
        
        dlg = Dialog('--- Auswahl ---',VBox([HBox([Label('Notenzeile:', width = 20),layoutSelect]),
                                             HBox([Label('Stimme:', width = 20),voiceSelect]),
                                             HBox([Label('Pausen expandieren:', width = 20), expandRest])], padding = 8))
        if dlg.run():
            self.staffLayout = staffLayouts[layoutSelect.value()]
            for staffLayout in staves.getElementsByTagName('staffLayout'):
                if staffLayout.getAttribute('description') == self.staffLayout:
                    self.layoutNode = staffLayout.cloneNode(True)
                    self.voiceNumber = max(eval(voiceSelect.value()) - 1, 0)
                    self.expandRest = expandRest.value()
                    break
        else:
            self.staffLayout = ''            
        
                    
    def appendVoice(self,score):
        # StaffLayout hinzufügen
        layout = score.gotoChild('layout')
        staves = layout.gotoChild('staves')

        staffLayouts = []
        for staffLayout in staves.getElementsByTagName('staffLayout'):
            staffLayouts.append(staffLayout.getAttribute('description'))
        while self.staffLayout in staffLayouts:
            self.staffLayout = self.staffLayout + '_1'
            
        self.layoutNode.setAttribute('description', self.staffLayout)
        staves.appendChild(self.layoutNode)

        # Ersten Schlüssel und Tonart bestimmen
        if len(self.chordList) > 5:
            for no in self.chordList[0:5]:
                if no.tagName == 'clefSign':
                    self.actClef = no.getAttribute('clef')
                    break
            for no in self.chordList[0:5]:
                if no.tagName == 'keySign':
                    self.actKey = no.getAttribute('fifths')
                    break
        

        # Voice hinzufügen
        for system in score.getElementsByTagName('system'):
            staves = system.gotoChild('staves')
            staff = staves.gotoChild('staff',new = True)
            staff.setAttribute('defaultTime',self.actMeter)
            staff.setAttribute('layout',self.staffLayout)
            voices = staff.gotoChild('voices')
            voice = voices.gotoChild('voice',new = True)
            voice.appendChild(self.lyricsSettings.cloneNode(True))
            noteObjects = voice.gotoChild('noteObjects')
            clefSign = noteObjects.gotoChild('clefSign')
            clefSign.setAttribute('clef',self.actClef)
            if self.actKey <> '0':    # Fuer C-Dur ist keine Tonart erforderlich
                keySign = noteObjects.gotoChild('keySign')
                keySign.setAttribute('fifths',self.actKey)

            systemDuration = getSystemDuration(system)
            dur = Rational(0)            
            while self.chordPointer < len(self.chordList) and  dur < systemDuration :
                no = self.chordList[self.chordPointer].cloneNode(True)
                if no.tagName in ['chord','rest']:
                    dur += getChordDuration(no, self.actMeter)
                    noteObjects.appendChild(no)
                elif no.tagName == 'keySign':
                    if self.actKey <> no.getAttribute('fifths'):
                        self.actKey = no.getAttribute('fifths')
                        noteObjects.appendChild(no)
                elif no.tagName == 'clefSign':
                    if self.actClef <> no.getAttribute('clef'):
                        self.actClef = no.getAttribute('clef')
                        noteObjects.appendChild(no)
                elif no.tagName == 'timeSign':
                    self.actMeter = no.getAttribute('time')
                    noteObjects.appendChild(no)
                else:                    
                    noteObjects.appendChild(no)
                self.chordPointer += 1

            
            while self.chordPointer < len(self.chordList) and   self.chordList[self.chordPointer].tagName == 'barline':
                no = self.chordList[self.chordPointer].cloneNode(True)
                noteObjects.appendChild(no)
                self.chordPointer += 1
                            
                
        # den Rest an letzte Notenzeile anhängen
        while self.chordPointer < len(self.chordList):
            no = self.chordList[self.chordPointer].cloneNode(True)
            noteObjects.appendChild(no)
            self.chordPointer += 1

    def removeGrafics(self):
        # entfernt alle Textfelder und Metafiles
        for no in self.chordList:
            if no.tagName in ['chord','rest']:
                for richText in no.getElementsByTagName('richText'):
                    richText.parentNode.parentNode.removeChild(richText.parentNode)
                for metafile in no.getElementsByTagName('metafile'):
                    metafile.parentNode.parentNode.removeChild(metafile.parentNode)
                for drawObjects in no.getElementsByTagName('drawObjects'):
                    if not drawObjects.hasChildNodes():
                        drawObjects.parentNode.removeChild(drawObjects)
            

def getNoteObjects(voice):
    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 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 getChordDuration(chord, meter):
    if meter in ['C','allaBreve']:
        _meter = '4/4'
    else:
        _meter = meter

    if chord.getElementsByTagName('duration').length == 0:
        return Rational('0')
    duration = chord.gotoChild('duration')
    if duration.hasAttribute('noDuration') and duration.getAttribute('noDuration') == 'true':
        return Rational('0')

    base = str(duration.getAttribute('base'))
    if '/' not in base:
        dur = Rational(base) * Rational(str(_meter))
    else:
        dur = Rational(base)
        
    for tuplet in duration.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
        dur = dur * factor
        # tuplet.setAttribute('count','0')   # Ausweg, wenn tuplet gelöscht wird, dann hängt capella !!
        # tuplet.setAttribute('tripartite','false')
        # tuplet.setAttribute('prolong','false')
        
        
    if duration.hasAttribute('dots'):
        d = duration.getAttribute('dots')
        if d == '1':
            dur = dur * Rational('3/2')
        elif d == '2':
            dur = dur * Rational('7/4')
        elif d == '3':
            dur = dur * Rational('15/8')
    return dur


def getSystemDuration(system):
    staves = system.gotoChild('staves')
    staff = staves.gotoChild('staff')
    defaultTime = staff.getAttribute('defaultTime')
    if defaultTime in ['C','allaBreve']:
        defaultTime = '4/4'
    voices = staff.gotoChild('voices')
    voice = voices.gotoChild('voice')
    childNodes = getNoteObjects(voice)
    voiceDuration = Rational(0)
    for childNode in childNodes:
        if childNode.nodeType == childNode.ELEMENT_NODE:
            if childNode.tagName == 'timeSign':
                defaultTime= childNode.getAttribute('time')
                if defaultTime in ['C','allaBreve']:
                    defaultTime = '4/4'
            if childNode.tagName in ['chord','rest']:
                voiceDuration += getChordDuration(childNode, defaultTime)
    return voiceDuration


vc = voiceCollector()

def changeDoc1(score):
    vc.getLayout(score)
    if vc.staffLayout == '':
        pass
    else:
        vc.collectVoice(score)
        vc.removeGrafics()

def changeDoc2(score):
    if vc.voiceFound:
        vc.appendVoice(score)

# Hauptprogramm:
from caplib.capDOM import ScoreChange
import tempfile

class ScoreChange1(ScoreChange):

    def changeScore(self, score):
        global doc
        doc = score.parentNode
        changeDoc1(score)

class ScoreChange2(ScoreChange):

    def changeScore(self, score):
        changeDoc2(score)

        
if activeScore():
    activeTitle = activeScore().title()
    dlg = FileDialog()
    dlg.__init__(bOpen=True)
    dlg.setTitle('Capella Datei für Import auswählen')
    dlg.addFilter('Capella-Datei', '*.cap')
    dlg.setStartFile('*.cap')
    if dlg.run():
        capFileName = dlg.filePath()
        head, tail = os.path.split(capFileName)
        # Bei import aus aktiver Datei stürzt capella mit diesem Skript ab
        if tail <> activeTitle:
            openScore(capFileName)
            tempInput = tempfile.mktemp('.capx')
            activeScore().write(tempInput)
            ScoreChange1(tempInput)
            os.remove(tempInput)
            closeActiveScore()

            activeScore().registerUndo("Voice_Import")
            tempInput2 = tempfile.mktemp('.capx')
            tempOutput2 = tempfile.mktemp('.capx')
            activeScore().write(tempInput2)

            ScoreChange2(tempInput2, tempOutput2)
            
            activeScore().read(tempOutput2)
            os.remove(tempInput2)
            os.remove(tempOutput2)
        else:
            messageBox('Voice Import', 'Hinweis:\nAus aktiver Partitur kann\nnicht importiert werden!', img=3)

