# -*- coding: ISO-8859-1 -*- """ capellaScript -- Copyright (c) 2003,2004,2005,2006 Peter Becker >>> Vorzeichenschieber Korrigiert sich überlappende Vorzeichen in 2-stimmigen Zeilen|| Passt die Position der Unterstimme bei Sekund- und Primabstand an|| Verschiebt Noten bei Stimmenkreuzung|| Unterdrückt bei gleicher Tonhöhe die untere Punktierung|| || Bei Problemen bitte eine Mail an peter_becker@freenet.de <<< History: 06.05.04 - Erste Ausgabe 07.05.04 - vertikaler Notenabstand berücksichtigt 07.05.04 - Absturz wenn nur eine Stimme pro Zeile 08.05.04 - berücksichtigen von erzwungenen Vorzeichen Bei Sekundabständen zwischen Unter und Oberstimme wird die Unterstimme verschoben und eventuelle Vorzeichen angepasst 09.05.04 - Datenmodellfehler ( Stepup4 ) Auflösung in Tonart und Shift nicht berücksichtigt 15.05.04 - Unterdrückung der Punktierung bei gleicher Tonhöhe 18.05.04 - verschieben der Note bei Stimmenkreuzung und Sekund Abstand Stimmenkreuzung 19.05.04 - gleiche Tonhöhe unterschiedlicher Wert oder ganze Note dann Note verschieben 25.05.04 - Notenverschiebung bei Prim optimiert 26.05.04 - Vorzeichen bei Prim optimiert 03.06.04 - Absturz in CompVoice beseitigt 19.06.04 - Auflösung von Extra Vorzeichen im Takt erkennen Auflösung nach erzwungenem Vorzeichen für den ganzen Takt erkennen 27.07.05 - Position bei ganzer Note in Oberstimme korrigiert 18.02.06 - erster Ansatz um Stimmenkreuzungen besser zu formatieren 20.02.06 - Verbesserung bei Stimmenkreuzung, Capella Bug head/alter/xShift nur ganzzahlig 02.03.06 - Überarbeitung wegen Capella 5.2. Datenmodellfix für Alter/xShift 20.03.06 - Erweiterte Primverschiebung auswählbar 30.03.06 - Formatierungsverbesserung bei Punktierungen 05.05.06 - Absturz wenn gleicher Ton und Vorzeichen der Tonart aufgelöst 31.08.06 - Vorzeichenerkennung verbessert ( hasAlter ), Korrektur in der Vorzeichenposition 21.10.07 - Vorzeichenerkennung verbessert ( hasAlter ), bei forcierter Auflösung 20.09.08 - stemDir Prüfung testweise ausgeschaltet """ import xml.dom, tempfile from caplib.capDOM import ScoreChange from xml.dom.minidom import NodeList, Node, Element doc = [] # parentNode von score def addElementNode(el,tagName): # Neue Node zu "el" hinzufügen wenn Node "tagName" nicht existiert # ansonsten existierende Node zurückmelden 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 getElementObjects(objList): # gibt eine Liste zurück newList = NodeList() for n in range(objList.length): if objList[n].nodeType == objList[n].ELEMENT_NODE: newList.append(objList[n]) return newList def getLastOccurence(tk, pi): # letztes Auftreten der Note im Takt suchen last = tk.pop() # eigene Note entfernen lastOc = '00000' while len(tk) > 0: last = tk.pop() if last[0:2] == pi[0:2]: lastOc = last break return lastOc def hasAlter(pi,al,hd,note,obj,merker): # Hat die Note ein Vorzeichen ? tk = merker[:] # Liste clonen lo = getLastOccurence(tk, pi) # letztes Auftreten der Note suchen takt1 = [] list0 = [ ] list1 = ['F'] list2 = ['F','C'] list3 = ['F','C','G'] list4 = ['F','C','G','D'] list5 = ['F','C','G','D','A'] list6 = ['F','C','G','D','A','E'] list7 = ['F','C','G','D','A','E','B'] listM1 = ['B'] listM2 = ['B','E'] listM3 = ['B','E','A'] listM4 = ['B','E','A','D'] listM5 = ['B','E','A','D','G'] listM6 = ['B','E','A','D','G','C'] listM7 = ['B','E','A','D','G','C','F'] key = obj.curKey() if key == 0: liste = list0 elif key == 1: liste = list1 elif key == 2: liste = list2 elif key == 3: liste = list3 elif key == 4: liste = list4 elif key == 5: liste = list5 elif key == 6: liste = list6 elif key == 7: liste = list7 elif key == -1: liste = listM1 elif key == -2: liste = listM2 elif key == -3: liste = listM3 elif key == -4: liste = listM4 elif key == -5: liste = listM5 elif key == -6: liste = listM6 elif key == -7: liste = listM7 if pi[0] in liste: # Ton hat in der Tonart normalerweise ein Vorzeichen #messageBox('HasAlter','norm VZ') vz = 0 if al: #messageBox('HasAlter','al') if al[0].hasAttribute('step'): # Vorzeichen ist nicht aufgelöst #messageBox('HasAlter','al step') if lo[0:2] == pi[0:2] and lo[2:4] == '00': # letztes Auftreten war aufgelöst vz = 1 # dann wird Vorzeichen angezeigt else: if al[0].hasAttribute('display'): df = al[0].getAttribute('display') # ansonsten wird else: df = 'none' # Vorzeichen nur angezeigt if df == 'force': vz = 1 # wenn FORCE gesetzt ist else: vz = 0 else: #messageBox('HasAlter','al no step') if al[0].hasAttribute('display'): df = al[0].getAttribute('display') # ansonsten wird else: df = 'none' # Vorzeichen nur angezeigt if df == 'force': vz = 1 # wenn FORCE gesetzt ist else: vz = 0 else: # Vorzeichen ist aufgelöst ! #messageBox('HasAlter','al aufgelöst') if lo[0:2] == pi[0:2] and lo[2:4] == '00': # letztes Auftreten war aufgelöst vz = 0 # dann kein Vorzeichen else: # erstes Auftreten daher wegen Fehler im Datenmodell alter = addElementNode(hd[0],'alter') # auf force setzen (Auflösung in Tonart ) alter.setAttribute('display','force') # ALTER Tag durch Datenmodell Fehler erforderlich ! vz = 1 else: # Ton hat in der Tonart normalerweise kein Vorzeichen vz = 0 #messageBox('HasAlter','norm kein VZ') if al: if al[0].hasAttribute('step'): # Vorzeichen da if lo[0:2] == pi[0:2] and lo[2:4] == '00': # letztes Auftreten war aufgelöst vz = 1 # dann wird Vorzeichen angezeigt elif lo[0:2] == '00' and lo[2:4] == '00': # kein vorheriges Vorzeichen im Takt vz = 1 # dann anzeigen st = str(al[0].getAttribute('step')) # if st == '1': st = '+1' if st <> lo[2:4]: # fall Kreuz, aber vorher im Takt b, oder umgekehrt vz = 1 if al[0].hasAttribute('display'): df = al[0].getAttribute('display') # ansonsten wird else: df = 'none' # Vorzeichen nur angezeigt if df == 'force': vz = 1 # wenn FORCE gesetzt ist #messageBox('Step',str(vz) + '\nlo=' + str(lo)+ '\npi=' + str(pi) + '\ntk=' + str(tk) + '\nst=' + str(st) + '\nl1=' + str(lo[2:4]) + '\ndf=' + str(df)) else: if al[0].hasAttribute('display'): df = al[0].getAttribute('display') # ansonsten wird else: df = 'none' # Vorzeichen nur angezeigt if df == 'force': vz = 1 # wenn FORCE gesetzt ist else: vz = 0 #messageBox('kein Step',str(vz) + '\nlo=' + str(lo)+ '\npi=' + str(pi) + '\ntk=' + str(tk)) else: # kein Vorzeichen if lo[0:2] == pi[0:2] and lo[2:4] <> '00': # letztes Auftreten war mit Vorzeichen vz = 1 # dann mit Auflösungszeichen alter = addElementNode(hd[0],'alter') # erstes Auftreten daher wegen Fehler im Datenmodell alter.setAttribute('display','force') # auf force setzen (Auflösung in Tonart ) ALTER Tag durch Datenmodell Fehler erforderlich ! else: vz = 0 #messageBox('kein Alter',str(vz) + '\nlo=' + str(lo)+ '\npi=' + str(pi) + '\ntk=' + str(tk)) #messageBox("vzret",str(vz)) return vz def compVoice(sy,st,dVo1,dVo2): global merker1, merker2, resetMerker1, resetMerker2, formatTyp system = activeScore().system(sy) staff = system.staff(st) voice1 = staff.voice(0) voice2 = staff.voice(1) anz1 = voice1.nNoteObjs() anz2 = voice2.nNoteObjs() n1 = 0 n2 = 0 noten1 = getElementObjects(dVo1.getElementsByTagName('noteObjects')[0].childNodes) noten2 = getElementObjects(dVo2.getElementsByTagName('noteObjects')[0].childNodes) list0 = [ ] list1 = ['F'] list2 = ['F','C'] list3 = ['F','C','G'] list4 = ['F','C','G','D'] list5 = ['F','C','G','D','A'] list6 = ['F','C','G','D','A','E'] list7 = ['F','C','G','D','A','E','B'] listM1 = ['B'] listM2 = ['B','E'] listM3 = ['B','E','A'] listM4 = ['B','E','A','D'] listM5 = ['B','E','A','D','G'] listM6 = ['B','E','A','D','G','C'] listM7 = ['B','E','A','D','G','C','F'] while n1 < anz1: obj1 = voice1.noteObj(n1) if resetMerker1 == 1: # falls Taktstrich da war Listen löschen merker1 = [] merker2 = [] resetMerker1 = 0 if obj1.subType() == NoteObj.CHORD: # Liste mit den Noten des Taktes der Oberstimme erzeugen note1 = noten1[n1] hd1 = note1.getElementsByTagName('head') du1 = note1.getElementsByTagName('duration') al1 = note1.getElementsByTagName('alter') pi1 = hd1[0].getAttribute('pitch') if al1: fo1 = al1[0].getAttribute('display') # Format des Listenelements : else: fo1 = '0' if al1: st1 = al1[0].getAttribute('step') # NoStF else: st1 = '00' # No = Note z.B. A5 if st1 == '1': st1 = '+1' # St = Step ( 00 = kein, +1 = halber Ton höher, -1 = halber Ton tiefer ) elif st1 == '-1': st1 = '-1' # F = erzwungenes Vorzeichen ( 0 = kein, F = erzwungenes Vorzeichen ) elif st1 <> '1' or st1 <> '-1': st1 = '00' if fo1 == 'force': fo1 = 'F' else: fo1 = '0' no1 = pi1 + st1 + fo1 merker1.append(no1) else: merker1 = [] # falls keine Note oder Pause neuen Takt beginnen merker2 = [] n1 = n1 + 1 continue if obj1.implBarline(): # impliziten Taktstrich merken resetMerker1 = 1 if obj1.subType() == NoteObj.CHORD: # Position der Note in der Oberstimme merken opo1 = obj1.posX() while n2 < anz2: obj2 = voice2.noteObj(n2) if resetMerker2 == 1: # falls Taktstrich da war Listen löschen merker2 = [] resetMerker2 = 0 if obj2.implBarline(): resetMerker2 = 1 # impliziten Taktstrich merken if obj2.subType() == NoteObj.CHORD: opo2 = obj2.posX() note2 = noten2[n2] hd2 = note2.getElementsByTagName('head') du2 = note2.getElementsByTagName('duration') al2 = note2.getElementsByTagName('alter') pi2 = hd2[0].getAttribute('pitch') if al2: fo2 = al2[0].getAttribute('display') # Format des Listenelements : else: fo2 = '0' if al2: st2 = al2[0].getAttribute('step') # NoStF else: st2 = '00' # No = Note z.B. A5 if st2 == '1': st2 = '+1' # St = Step ( 00 = kein, +1 = halber Ton höher, -1 = halber Ton tiefer ) elif st2 == '-1': st2 = '-1' # F = erzwungenes Vorzeichen ( 0 = kein, F = erzwungenes Vorzeichen ) elif st2 <> '1' or st2 <> '-1': st2 = '00' if fo2 == 'force': fo2 = 'F' else: fo2 = '0' no2 = pi2 + st2 + fo2 merker2.append(no2) dpi1 = obj1.diatonicPitch(0)[0] #nur die Tonhöhe entnehmen dpi2 = obj2.diatonicPitch(0)[0] Ddpi = dpi1 - dpi2 if opo1 == opo2: #wenn die horizontale Position gleich ist ba2 = du2[0].getAttribute('base') if du2[0].hasAttribute('dots'): dt2 = du2[0].getAttribute('dots') else: dt2 = 'none' ba1 = du1[0].getAttribute('base') if du1[0].hasAttribute('dots'): dt1 = du1[0].getAttribute('dots') else: dt1 = 'none' offset = '0' if dpi1 > dpi2: offset='0' # Stimmenkreuzung Noten positionieren elif dpi1 < dpi2: offset='-0.75' # Unterstimme nach links if Ddpi == -1: offset = '-1' # wenn Sekundabstand, weiter nach links if offset <> '0': display = addElementNode(note2,'display') display.setAttribute('xShift',str(offset)) vz1 = hasAlter(pi1,al1,hd1,note1,obj1,merker1) # hat die Oberstimme ein Vorzeichen ? vz2 = hasAlter(pi2,al2,hd2,note2,obj2,merker2) # hat die Unterstimme ein Vorzeichen ? #messageBox('VZ',str(vz1) + str(vz2) + '\n' + str(Ddpi)) #if vz1 == 1 and vz2 == 1: # falls Vorzeichen, dann verschieben 29.8.2006 #if vz1 == 1: # komplett auf Kommentar 31.8.2006 # al1 = note1.getElementsByTagName('alter') # if Ddpi == -1: al1[0].setAttribute('xShift','-1.0') # else: al1[0].setAttribute('xShift','-0.75') if pi1 == pi2 and dt1 == dt2 and formatTyp == 0: # falls gleicher Ton und punktiert dis = addElementNode(note2,'display') # Punkt unten weg dis.setAttribute('dotsInvisible','true') #vz2 = hasAlter(pi2,al2,hd2,note2,obj2,merker2) # hat die untere Note ein Vorzeichen ? #vz1 = hasAlter(pi1,al1,hd1,note1,obj1,merker1) # hat die obere Note ein Vorzeichen ? #messageBox('VZ',str(vz1) + ' ' + str(vz2)) if vz1 == 1 and vz2 == 1 and Ddpi < 7 and Ddpi <> 0: # nur wenn unten und oben Vorzeichen sind al2 = note2.getElementsByTagName('alter') al2[0].setAttribute('xShift','-0.75') #if Ddpi == 1 or Ddpi == -1: # bei Sekundabstand if Ddpi == 1: # bei Sekundabstand ohne Stimmenkreuzung if Ddpi == 1: offset = '1.0' # Note verschieben else: offset = '1.25' if ba1 == '1/1': offset = '1.5' # wenn ganze Note in Oberstimme 28.7.2005 display = addElementNode(note2,'display') display.setAttribute('xShift',str(offset)) if vz2 == 1 and al2: # und Vorzeichen ebenfalls if float(al2[0].hasAttribute('xShift')): xS = float(al2[0].getAttribute('xShift')) xS=xS-1.25 # 31.8.2006 else: xS = -1 if vz1 == 0: xS = -0.75 #else: xS = xS - 0.25 al2[0].setAttribute('xShift',str(xS)) elif vz2 == 1: # 1. Auflösung und Sekundabstand alter = addElementNode(hd2[0],'alter') # der Abstand der Noten zu klein ist und keine Prim alter.setAttribute('display','force') # Manchmal ist kein Alter Tag da, deshalb forcen al2 = note2.getElementsByTagName('alter') al2[0].setAttribute('xShift','-0.25') if Ddpi == 2: # bei Terzabstand ohne Stimmenkreuzung #messageBox('VZ',str(vz1) + str(vz2) + '\n' + str(Ddpi)) if vz1 == 1 and vz2 == 1: # und beide Noten mit Vorzeichen if al1 and al2: # und Alter Tag al1 = note1.getElementsByTagName('alter') al1[0].setAttribute('xShift','-1') al2 = note2.getElementsByTagName('alter') al2[0].setAttribute('xShift','0') d1 = 0 # Punktierung merken d2 = 0 if du1[0].hasAttribute('dots'): d1 = 1 if du2[0].hasAttribute('dots'): d2 = 1 if Ddpi == 0: # wenn Primeabstand if ba1 == '1/1' or ba2 == '1/1': offset = '1.5' # und ganze Note elif ba1 <> ba2: offset = '1.25' # oder Wert ungleich elif ba1 == ba2 and formatTyp == 0: # normale Primverschiebung, Wert gleich if d1 == 0 and d2 == 1: offset = '1.25' elif d1 == 1 and d2 == 0: offset = '-1.25' elif ba1 == ba2 and formatTyp == 1: # erweiterte Primverschiebung, Wert gleich if d1 == 0 and d2 == 1: offset = '1.25' elif d1 == 1 and d2 == 0: offset = '-1.25' elif d1 == 1 and d2 == 1: offset = '1.75' else: offset = '1.25' if offset <> 0: display = addElementNode(note2,'display') display.setAttribute('xShift',str(offset)) if vz1 == 1 and vz2 == 1: # wenn beide Noten ein Vorzeichen haben if al2: al2[0].setAttribute('display','suppress') # dann eines unterdrücken else: alter = addElementNode(hd2[0],'alter') alter.setAttribute('display','suppress') n2 = n2 + 1 n1 = n1 + 1 n2 = 0 def changeDoc(score): global doc, merker1, merker2, resetMerker1, resetMerker2 merker1 = [] #leere Liste merker2 = [] resetMerker1 = 0 resetMerker2 = 0 doc = score.parentNode systems = score.getElementsByTagName('system') for sy in range(systems.length): staves = systems[sy].getElementsByTagName('staff') #erzeugt Liste mit DOM Elementen for st in range(staves.length): voices = staves[st].getElementsByTagName('voice') #erzeugt Liste mit DOM Elementen vl = voices.length if vl > 1: #nur wenn mindestens 2 Stimmen voice1 = voices[0] #DOM Element extrahieren voice2 = voices[1] #DOM Element extrahieren #if voice1.hasAttribute('stemDir') and voice1.getAttribute('stemDir') == 'up': # if voice2.hasAttribute('stemDir') and voice2.getAttribute('stemDir') == 'down': result = compVoice(sy,st,voice1,voice2) def vzDialog(): global formatTyp options = ScriptOptions() opt = options.get() placeholder1 = Label(' ',width=1) placeholder2 = Label(' ',width=1) placeholder3 = Label(' ',width=1) placeholder4 = Label(' ',width=1) radFormatTyp = Radio(['normale Primverschiebung','erweiterte Primverschiebung'],text='Formatierungsoption auswählen',value=int(opt.get('FormatTyp','0'))) box = VBox([radFormatTyp,placeholder1],padding=1) dlg = Dialog('Vorzeichenschieber', box) if dlg.run(): formatTyp = radFormatTyp.value() options.set(opt) return True else: return False # Hauptprogramm: from caplib.capDOM import ScoreChange import tempfile class ScoreChange(ScoreChange): def changeScore(self, score): changeDoc(score) if activeScore(): if vzDialog(): activeScore().registerUndo("Vorzeichen_Schieber") tempInput = tempfile.mktemp('.capx') tempOutput = tempfile.mktemp('.capx') activeScore().write(tempInput) ScoreChange(tempInput, tempOutput) activeScore().read(tempOutput) os.remove(tempInput) os.remove(tempOutput)