# -*- coding: UTF-8 -*-
""" capellaScript -- (C) 2025 Ken Haiker, acaMusic (www.acamusic.de)
                         Implementation: Brian Schueler
>>> acaLead
    Rev 3.5.10 (23.07.2026) --- Plugin für Gitarristen (und andere Saiteninstrumente wie Bass, Ukulele usw.)
    ||
    Das Problem: Das Griffbrett der Gitarre ist über dem vierten Bund “nicht eindeutig”.
	|Nach Noten auf den höheren Bünden der Gitarre spielen ist deshalb nach der klassischen Lage-Methode nur stark
	eingeschränkt möglich. Nach der Methode von "acaLead Notation (aLN)" ist das jedoch 
	KINDERLEICHT, denn durch die Anwendung von aLN wird das höhere Griffbrett nicht nur eindeutig, sondern auch 
	informationsvollständig (das heißt, es wird keine zusätzliche Tabulatur mehr benötigt). 
	||
	Weitere Informationen, ein ausführliches Plugin-Handbuch sowie viele bekannte klassische Gitarrenstücke, 
	die in acaLead Notation geschrieben sind, gibt es auf der Homepage
	||
	-------------   www.acaMusic.de   -------------
	||
	Das Plugin konvertiert die "relativen" acaLead Gitarren-Stimme(n) einer aLN-Notenzeile in zusätzliche "absolute" 
	Stimmen, die musikalisch richtig in Capella abgespielt werden können.
	|Durch die Methode von acaLead Notation (aLN) lassen sich die Töne/Noten des gesamten, nicht eindeutigen 
	Griffbrettes einer Gitarre (auch Bass, Ukulele, Mandoline usw.) informationsvollständig auf die Noten der leicht 
	spielbaren unteren vier Bünde reduzieren. Beziehungsweise lassen sich die unteren vier Bünde und Leersaiten auf 
	die höheren Bünde vollständig abstrahieren.
	||Das Spielen nach Noten auch auf den höheren Bünden - sowohl in leichten einstimmigen als auch in sehr komplexen 
	mehrstimmigen Stücken - wird dadurch sehr leicht, weil man als Gitarrist nur die Noten der untersten vier Bünde
	und Leersaiten kennen muss.
<<<

History:  Mai 2025 - Erste Version

"""

import pprint
import tempfile
import cPickle as pickle
from caplib.capDOM import ScoreChange, childElements
from xml.dom.minidom import NodeList, Node, Element

sys.stdout = open(tempfile.gettempdir()+'capella-acalead.log', 'w')

# Get object list from element
# (From guitarrero.py)
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

# Get the given element node, it not a new can be created
# (From chordExpansion.py)
def gotoChild(self, name, new=False):
	newEl = None
	for child in self.childNodes:
		if child.nodeType == child.ELEMENT_NODE and child.tagName == name:
			newEl = child
			break

	if new == False:
		return newEl

	if newEl == None:
		newEl = doc.createElement(name)
		self.appendChild(newEl)
	return newEl
import new
Node.gotoChild = new.instancemethod(gotoChild,None,Node)

# Get the base child object and the child list of an object
def getChildren(el, name):
	if name == 'drawObj':
		namePlural = 'drawObjects'
	elif name == 'staff':
		namePlural = 'staves'
	else:
		namePlural = name+'s'
	plElems = childElements(el, namePlural)
	if len(plElems) > 0:
		plFirstElem = plElems[0]
		retval = childElements(plFirstElem, name)
		return (plFirstElem, retval)
	else:
		xElem = el.getElementsByTagName(name)
		if xElem.length > 0:
			x = xElem[0]
			childNodes = getElementObjects(x.childNodes)
			return (x, childNodes)
		else:
			return (None, [])

# Replace a named child tree
def setChildren(el, children, name):
	oldchildren = childElements(el, name)
	for oldchild in oldchildren:
		el.removeChild(oldchild)
	for child in children:
		el.appendChild(child)

def getDurationByBase(base, dots = 0, tuplet = 0):
	base = base.split('/')
	value = 0
	if len(base) == 2:
		value = float(base[0])/float(base[1])
	else:
		value = float(base[0])

	# Tuplet Note
	if tuplet > 0:
		if tuplet == 3:
			value = value * 2.0 / 3.0

	# Dotted Note
	dot_value = value
	if dots > 0:
		for i in range(0,dots):
			dot_value = dot_value / 2
			value = value + dot_value
	return value

# Get the decimal duration of a note/chord/rest
def getObjDuration(noteObj):
	duration = noteObj.getElementsByTagName('duration')
	# Only NoteObjects that have a duration
	if len(duration) > 0:
		noDuration = duration[0].getAttribute('noDuration')
		if noDuration == 'true':
			return 0.0

		baseT = duration[0].getAttribute('base')
		if baseT is None or baseT == "":
			return 0.0

		dots = duration[0].getAttribute('dots')
		if dots is None or dots == '':
			dots = '0'

		tuplet = '0'
		tupletNode = duration[0].gotoChild('tuplet', False)
		if tupletNode is not None:
			tuplet = tupletNode.getAttribute('count')

		return getDurationByBase(baseT, int(dots), int(tuplet))
	return None

# Get the total duration of a voice
def getVoiceTotalTime(voice):
	totalTime = 0.0
	onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
	for obj in noteObjects:
		duration = getObjDuration(obj)
		if duration is not None:
			totalTime = totalTime + duration
	return totalTime

# Get the total duration of a staff
def getStaffTotalTime(staff):
	totalTime = 0.0
	ovoices, voices = getChildren(staff, 'voice')
	for voice in voices:
		tt = getVoiceTotalTime(voice)
		totalTime = max(totalTime, tt)
	return totalTime

def hasStaffAlnMark(staff):
	staffName = staff.getAttribute('layout')
	if staffName == fretStaffName:
		return True
	return False

# Get the placement (global/local) and Fret Index text of a noteObject and
# set converted Mark when Keyword found
def processObjText(noteObj, filter = None, cvtMark="!", newX = None):
	global estFretIndexFontSize
	global estFretIndexFontColor

	retval = (None, None)
	oDrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
	for drawObj in drawObjs:
		basic = drawObj.gotoChild('basic', False)
		text = drawObj.gotoChild('text', False)
		if basic is not None:
			place = basic.getAttribute('placementHint')
		else:
			place = ''
		content = ''
		if text is not None:
			content_t = text.getElementsByTagName('content')[0].firstChild
			if content_t is not None:
				content = content_t.data
			font = text.gotoChild('font', False)
			if font is not None:
				color = font.getAttribute('color')
				try:
					height = int(font.getAttribute('height'))
				except:
					height = 0
				if color == estFretIndexFontColor:
					if height == estFretIndexFontSize:
						place = 'globalFretIndex'
					elif height < estFretIndexFontSize:
						place = 'localFretIndex'
			if content.lower().replace("!","") == u'fret':
				contentEl = text.getElementsByTagName('content')[0].firstChild
				if cvtMark is not None:
					contentEl.replaceWholeText(content.replace("!","")+cvtMark)
				
			contentInt = None
			try:
				contentInt = int(content)
			except:
				pass
			if newX is not None and place == 'globalFretIndex' and contentInt is not None:
				text.setAttribute('x', str(newX))

			if ((filter == 'local' and place == 'localFretIndex') and content.lower().replace("!","") != u'fret') or \
			   ((filter == 'global' and place == 'globalFretIndex') and content.lower().replace("!","") != u'fret'):
				retval = (place, content)
	return retval

def getGlobalFretIndexPositions(staff, countOnly = False):
	ovoices, voices = getChildren(staff, 'voice')
	gfiPosList = []
	for voice in voices:
		pos = -0.001
		onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
		for obj in noteObjects:
			duration = getObjDuration(obj)
			if duration is not None:
				placement, text = processObjText(obj, 'global')
				if placement == 'globalFretIndex': 
					print('got global FI')
					fretIndex = text
					gfiPosList.append((pos, fretIndex))
				voltas = obj.getElementsByTagName('volta')
				if voltas.length > 0 and not countOnly:
					gfiPosList.append((pos-0.001, 'volta'))
				pos = pos + duration
	gfiPosList.sort()
	print("Global Fret Indexes", gfiPosList)
	return gfiPosList
		
# Generate Global Fret Index ranges of the Fret Index positions list
# initGpi is the initial Global Fret Index value from previous staff
def genGlobalFretIndexPosRanges(gfiPosList, totalTime, initGFI = [], countOnly = False):
	lastPos = None
	pos = -0.001
	retval = []
	globalFretIndex = initGFI
	fretIndex = 0
	if len(globalFretIndex) > 0:
		fretIndex = globalFretIndex[0]
	
	for gfiPos in gfiPosList:
		text = gfiPos[1]
		pos = gfiPos[0]
		if text == 'volta':
			if len(globalFretIndex) < 2:
				globalFretIndex.insert(0, fretIndex)
			else:
				oldFretIndex = fretIndex
				fretIndex = globalFretIndex[-1]
				globalFretIndex = globalFretIndex[1:]
				globalFretIndex[0] = oldFretIndex
			# Force Global Fret Index were present
			text = str(fretIndex)

		try:
			print('text', text)
			gf = 0
			if len(globalFretIndex) > 0:
				gf = globalFretIndex[0]

			# Relative Global Fret Index (Up)
			if text[0] == '+':
				text = text[1:]
				fretIndex = gf + int(text)
			# Relative Global Fret Index (Down)
			elif text[0] == '-':
				text = text[1:]
				fretIndex = gf - int(text)
			# Absolute Global Fret Index
			else:
				fretIndex = int(text)

			if len(globalFretIndex) > 0:
				retval.append(((lastPos, pos), globalFretIndex[:]))
			if len(globalFretIndex) == 0:
				globalFretIndex.append(fretIndex)
			else:
				globalFretIndex[0] = fretIndex
			print('l. fretIndex at this point:',fretIndex)
			print('g. fretIndex at this point:',globalFretIndex)
			lastPos = pos
		except:
			print('CRASH!!!')
			pass

	if len(globalFretIndex) > 0 and lastPos is not None:
		retval.append(((lastPos, totalTime), globalFretIndex[:]))
	else:
		if not countOnly:
			retval.append(((0.0, totalTime), globalFretIndex[:]))
	return retval
			
# Get current Global Fret Index at specific position
def getGlobalFretIndexAtPos(gfiprs, pos):
	for gfipr in gfiprs:
		if pos >= gfipr[0][0] and pos < gfipr[0][1]:
			return gfipr[1]
	return 0

# Generate Local Fret Indexes for a voice
#def genLocalFretIndexes(voice, gfiprs, localOnly = False):
def genGlobalFretIndexes(voice, gfiprs):
	pos = 0.0
	retval = []
	
	onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
	for obj in noteObjects:
		duration = getObjDuration(obj)
		
		if duration is not None:
			placement, text = processObjText(obj, 'local')
			fretIndex = getGlobalFretIndexAtPos(gfiprs, pos)
			if type(fretIndex) == list:
				if len(fretIndex) > 0:
					fretIndex = fretIndex[0]
				else:
					fretIndex = 0
			retval.append(fretIndex)
			pos = pos + duration
	return retval

# Convert Pitch to MIDI Note value
# (From capXNode.py, Capella 10)
def midiPitch(head):
	pitch = head.getAttribute('pitch')
	alters = head.getElementsByTagName('alter')
	i = 'CDEFGAB'.find(pitch[0])
	n = (0,2,4,5,7,9,11)[i] + 12 * int(pitch[1])
	for alter in alters:
		stepT = alter.getAttribute('step')
		if stepT is not None and stepT != "":
			print("stepT", stepT)
			step = int(stepT)
			n = n + step
	return n

# Inverse function to midiPitch
def pitchFromMidi(head, n):
	octave = int(n)/12
	itone = int(n)%12
	tonesSharp = 'CCDDEFFGGAAB'
	tonesFlat  = 'CDDEEFGGAABB'
	steps      = '010100101010'
	toneSharp = tonesSharp[itone]+str(octave)
	toneFlat  = tonesFlat[itone]+str(octave)
	step = -int(steps[itone])
	tone = toneFlat

	# Use Sharp sign if set
	if setSharp:
		tone = toneSharp
		step = -step

	if step != 0:
		alter = head.gotoChild('alter', True)
		alter.setAttribute('step', str(step))
	else:
		alter = head.getElementsByTagName('alter')
		if len(alter) > 0:
			head.removeChild(alter[0])
	head.setAttribute('pitch', tone)

def getSortedHeadsOfChord(chord):
	oheads, heads = getChildren(chord, 'head')
	m_pitches = []
	for head in heads:
		m_pitch = midiPitch(head)
		m_pitches.append(m_pitch)
	retval = []
	sorted_m_pitches = m_pitches
	sorted_m_pitches.sort()
	for sorted_m_pitch in sorted_m_pitches:
		index = m_pitches.index(sorted_m_pitch)
		retval.append(heads[index])
	return retval

def getObjTextObjsByCriteria(noteObj, criteria):
	texts = []
	if noteObj.localName == 'chord':
		odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
		if drawObjs is not None and len(drawObjs) > 0:
			for drawObj in drawObjs:
				if isTextCriteriaDrawObj(drawObj, criteria):
					texts.append(drawObj)
	return texts

def isTextCriteriaDrawObj(drawObj, criteria):
	text = drawObj.gotoChild('text', False)
	richText = drawObj.gotoChild('richText', False)
	basic = drawObj.gotoChild('basic', False)
	transposable = drawObj.gotoChild('transposable', False)
	group = drawObj.gotoChild('group', False)
	if transposable is not None:
		if criteria == 'chord':
			return True
		else:
			return False
	if group is not None: 
		drOb = group.gotoChild('drawObj')
		if drOb is not None:
			if criteria == 'chord':
				return True
			else:
				return False
	if text is not None:
		font = text.gotoChild('font', False)
		if font is not None:
			face = font.getAttribute('face')
			# Music Symbol Criteria (capella3 font face)
			if face == 'capella3':
				if criteria == 'musicSymbol':
					return True
				else:
					return False
			height = font.getAttribute('height')
			try:
				height = int(height)
			except:
				height = 0
			color = font.getAttribute('color')
			# Global Fret Index Criteria (font color and size)
			if color == estFretIndexFontColor and height == estFretIndexFontSize:
				if criteria == 'globalFretIndex':
					return True
				else:
					return False
			# Local Fret Index Criteria (font color and size)
			if color == estFretIndexFontColor and height < estFretIndexFontSize:
				if criteria == 'localFretIndex':
					return True
				else:
					return False
		# Fingering criteria (placementHint)
		if basic is not None and basic.getAttribute('placementHint') == 'fingering':
			if criteria == 'fingering':
				return True
			else:
				return False 
		# Simple Text (without placementHint) or Performance Instruction (placementHint)
		if (basic is None) or (basic is not None and basic.getAttribute('placementHint') in ['', 'performanceInstruction']) :
			if criteria == 'simple':
				return True
			else:
				return False
	# Rich Text (richText Child is present)
	if richText is not None:
		if criteria == 'richText':
			return True
		else:
			return False
	return False

def sortObjTextObjsByY(textObjs):
	retval = []
	ys = []
	sorted_ys = []
	for textObj in textObjs:
		text = textObj.gotoChild('text', False)
		if text is not None:
			y = float(text.getAttribute('y'))
			ys.append(y)
			sorted_ys.append(y)
	for y in ys:
		if ys.count(y) != 1:
			return []
	sorted_ys.sort(reverse=True)
	for y in sorted_ys:
		index = ys.index(y)
		retval.append(textObjs[index])
	return retval

def getTextContent(drawObj):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		content = ''
		content_t = text.getElementsByTagName('content')[0].firstChild
		if content_t is not None:
			content = content_t.data
		return content
	return None

# Transpose a single note/chord with given Offset
def transpose(chord, globalOffset, localOffsets):
	heads = getSortedHeadsOfChord(chord)
	numHeads = len(heads)
	offsetID = 0
	for head in heads:
		oper = 'set'
		localOffset = str(globalOffset)
		if numHeads == len(localOffsets):
			localOffset = localOffsets[offsetID]
		else:
			if len(localOffsets) == 0:
				localOffset = str(globalOffset)
			elif len(localOffsets) == 1:
				localOffset = localOffsets[0]
		if localOffset.startswith('-'):
			oper = 'add'
		elif localOffset.startswith('+'):
			oper = 'add'
		try:
			localOffset = int(localOffset)
		except:
			localOffset = 0
		if oper == 'add':
			offset = globalOffset + localOffset
		else:
			offset = localOffset
		oldpitch = head.getAttribute('pitch')
		oldalter = head.getElementsByTagName('alter')
		oldstep = 0
		if len(oldalter) > 0:
			try:
				oldstep = int(oldalter[0].getAttribute('step'))
			except:
				print("Error Transposing,", oldpitch, "by", offset)
		print("transposing", oldpitch, "+", oldstep, "by", offset)

		try:
			midP = midiPitch(head) + offset
			pitchFromMidi(head, midP)
		except:
			print("Error in Midi pitch:")

		pitch = head.getAttribute('pitch')
		alter = head.getElementsByTagName('alter')
		step = 0
		if len(alter) > 0:
			try:
				step = int(alter[0].getAttribute('step'))
			except:
				print("Error Transposing,", oldpitch, "by", offset)
		print("transposed to", pitch, "+", step)
		offsetID = offsetID + 1

def setTextColor(drawObj, new_color):
	text = drawObj.gotoChild('text', False)
	if text is None:
		return
	font = text.gotoChild('font', True)
	if new_color is None:
		color = font.getAttribute('color')
		if color is not None and color != '':
			font.removeAttribute('color')
	else:
		font.setAttribute('color', new_color)

def setTextObjFretFingeringVisibility(noteObj):
	fingeringTextObjs = getObjTextObjsByCriteria(noteObj, 'fingering')
	fretIndexTextObjs = getObjTextObjsByCriteria(noteObj, 'localFretIndex')
	oAllTextObjs, allTextObjs = getChildren(noteObj, 'drawObjects')
	valFingeringTextObjs = []
	valFretIndexTextObjs = []
	for fingeringTextObj in fingeringTextObjs:
		content = getTextContent(fingeringTextObj)
		if content is not None and content != '' and content.strip() != '-':
			valFingeringTextObjs.append(fingeringTextObj)
	for fretIndexTextObj in fretIndexTextObjs:
		content = getTextContent(fretIndexTextObj)
		if content is not None and content != '' and content.strip() != '-':
			valFretIndexTextObjs.append(fretIndexTextObj)

# Transpose voice by given Local Fret Indexes
def applyFretIndexTranspose(originalVoice, ghostVoice, globalFretIndexes):
	oorigObjs, origObjs = getChildren(originalVoice, 'noteObjects')
	ofretObjs, fretObjs = getChildren(ghostVoice, 'noteObjects')
	lfIndex = 0

	for i in range(0,len(origObjs)):
		origObj = origObjs[i]
		fretObj = fretObjs[i]
		duration = getObjDuration(origObj)
		
		if duration is not None:
			# Remove Text of Ghost Voice
			ofretDrawObjects, fretDrawObjects = getChildren(fretObj, 'drawObjects')
			if fretDrawObjects is not None:
				for fretDrawObject in fretDrawObjects:
					oGroupElements, groupElements = getChildren(fretDrawObject,'group')
					keep = False
					for groupElement in groupElements:
						if groupElement is not None:
							gdoPolygon = groupElement.gotoChild('polygon')
							if gdoPolygon is not None:
								gdoPolygon.setAttribute('fillColor', 'ffffff')
								keep = True
					if not keep:
						ofretDrawObjects.removeChild(fretDrawObject)

			# Hide Note
			fretDisplay = fretObj.gotoChild('display', True)
			if setHide:
				fretDisplay.setAttribute('invisible', 'true')
			fretHeads = fretObj.getElementsByTagName('head')
			for fretHead in fretHeads:
				fretHead.setAttribute('shape', 'diamond')

			# Mute Chords in Original Voice
			origHeads = origObj.getElementsByTagName('head')
			for origHead in origHeads:
				origHead.setAttribute('silent', 'true')

			# Disable collision avoidance
				origDisplay = origObj.gotoChild('display', True)
				if origDisplay is not None:
					if setCollision:
						origDisplay.setAttribute('noAutoXShift', 'true')
					else:
						try:
							origDisplay.removeAttribute('noAutoXShift')
						except:
							pass
				if not origDisplay.hasAttributes():
					origObj.removeChild(origDisplay)


			# Transpose notes in Ghost Voice
			if fretObj.localName == 'chord':
				setTextObjFretFingeringVisibility(origObj)
				localFretIndexTextObjs = getObjTextObjsByCriteria(origObj, 'localFretIndex')
				sortedLocalFretIndexTextObjs = sortObjTextObjsByY(localFretIndexTextObjs)
				sortedLocalFretIndexTexts = []
				for sortedLocalFretIndexTextObj in sortedLocalFretIndexTextObjs:
					sortedLocalFretIndexTexts.append(getTextContent(sortedLocalFretIndexTextObj))

				#transpose(fretObj, [localFretIndexes[lfIndex]])
				transpose(fretObj, globalFretIndexes[lfIndex], sortedLocalFretIndexTexts)

				fretHeads = fretObj.getElementsByTagName('head')
				for fretHead in fretHeads:
					if setSuppress:
						alter = fretHead.gotoChild('alter', True)
						if alter is not None:
							alter.setAttribute('display', 'suppress')
			# Disable collision avoidance
				display = fretObj.gotoChild('display', True)
				if display is not None:
					if setCollision:
						display.setAttribute('noAutoXShift', 'true')
					else:
						try:
							display.removeAttribute('noAutoXShift')
						except:
							pass
					stem = fretObj.gotoChild('stem', False)
					tremoloBars = None
					if stem is not None:
						tremoloBars = stem.getAttribute('tremoloBars')
					if tremoloBars is None:
						if setSmall:
							display.setAttribute('small', 'true')
						else:
							try:
								display.removeAttribute('small')
							except:
								pass

				if not display.hasAttributes():
					fretObj.removeChild(display)
				lfIndex = lfIndex + 1
			if fretObj.localName == 'rest':
				lfIndex = lfIndex + 1

	setChildren(oorigObjs, origObjs, 'noteObjects')
	setChildren(ofretObjs, fretObjs, 'noteObjects')

# Check whether staff is already in acaLead notation
def isStaff_aLN(staff):
	ovoices, voices = getChildren(staff, 'voice')
	voiceIndex = 0
	aLNlist = []
	for voice in voices:
		muted = isVoiceMuted(voice)
		print('is_muted', muted)
		hidden = isVoiceHidden(voice)
		print('is_hidden', hidden)
		if voiceIndex < len(voices)/2:
			aLNlist.append(muted == True and hidden == False)
		else:
			aLNlist.append(muted == False and hidden == True)
		voiceIndex = voiceIndex + 1
	if aLNlist.count(True) == len(aLNlist) and len(aLNlist) > 0:
		return True
	return False

# Check whether voice is hidden/invisible
def isVoiceHidden(voice):
	oObjs, objs = getChildren(voice, 'noteObjects')
	hiddenList = []
	for i in range(0,len(objs)):
		obj = objs[i]
		duration = getObjDuration(obj)
		
		if duration is not None:
			display = obj.gotoChild('display', False)
			if display is not None:
				hidden = display.getAttribute('invisible') == 'true'
				hiddenList.append(hidden)
			else:
				hiddenList.append(False)

	#print('hiddenList', hiddenList)
	if len(hiddenList) == hiddenList.count(True) and len(hiddenList) > 0:
		return True
	return False

# Unmute all notes in the voice
def unmuteVoice(voice):
	oObjs, objs = getChildren(voice, 'noteObjects')
	silenceList = []
	for i in range(0,len(objs)):
		obj = objs[i]
		duration = getObjDuration(obj)
		
		if duration is not None:
			heads = obj.getElementsByTagName('head')
			for head in heads:
				silent = head.getAttribute('silent') == 'true'
				if silent:
					head.removeAttribute('silent')
	
# Check whether all notes in the voice are muted
def isVoiceMuted(voice):
	oObjs, objs = getChildren(voice, 'noteObjects')
	silenceList = []
	for i in range(0,len(objs)):
		obj = objs[i]
		duration = getObjDuration(obj)
		
		if duration is not None:
			heads = obj.getElementsByTagName('head')
			for head in heads:
				silent = head.getAttribute('silent') == 'true'
				silenceList.append(silent)

	#print('silenceList', silenceList)
	if len(silenceList) == silenceList.count(True) and len(silenceList) > 0:
		return True
	return False
	
# Convert original Notation to playable acaLead Notation
def orgToALN(score, bypass_newVoices = False):
	osystens, systems = getChildren(score, 'system')
	globalFretIndex = []
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:	
			staffName = staff.getAttribute('layout')
			if staffName == fretStaffName:
				gfiPositions = getGlobalFretIndexPositions(staff)

				ovoices, voices = getChildren(staff, 'voice')
				newVoices = []
				totalTime = getStaffTotalTime(staff)
				gfipr = genGlobalFretIndexPosRanges(gfiPositions, totalTime, globalFretIndex)
				print("Global Fret Index Ranges", gfipr)

				vnum = 0
				for voice in voices:
					vnum = vnum + 1
					newVoice = voice.cloneNode(deep=True)
					# Remove Stem direction on Ghost Voice to avoid shifting Text
					if newVoice.hasAttribute("stemDir"):
						newVoice.removeAttribute("stemDir")
					if hasStaffAlnMark(staff) and not bypass_newVoices:
						newVoices.append(newVoice)
					gfis = genGlobalFretIndexes(voice, gfipr)
					applyFretIndexTranspose(voice, newVoice, gfis)
				voices.extend(newVoices)
				if len(gfipr) > 0:
					globalFretIndex = gfipr[-1][1]
				setChildren(ovoices, voices, 'voice')
				getVoiceTotalTime(voices[0])

# Convert acaLead Notation back to original Notation
def aLNToOrg(score):
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:	
			staffName = staff.getAttribute('layout')
			if staffName == fretStaffName:
				ovoices, voices = getChildren(staff, 'voice')
				if (isStaff_aLN(staff) or isAln) and hasStaffAlnMark(staff):
					nVoices = len(voices)
					if nVoices > 1 and nVoices % 2 == 0:
						keepVoices = voices[0:(nVoices/2)]
						for voice in keepVoices:
							unmuteVoice(voice)
							oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
							for noteObj in noteObjs:
								processObjText(noteObj, cvtMark = "")
						setChildren(ovoices, keepVoices, 'voice')
			
# Determine FretIndex color and font size - keyword "Fret"
def getFretColorAndSize(score):
	fretInfoStaves = []
	selected_staff = curSelection()[0][1]

	# Search for Fret Keyword in Note Objects
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		for staff in staves:	
			ovoices, voices = getChildren(staff, 'voice')
			staffName = staff.getAttribute('layout')
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				for i in range(0,len(objs)):
					obj = objs[i]
					texts = obj.getElementsByTagName('text')
					if texts.length > 0:
						for text in texts:
							content = ''
							content_t = text.getElementsByTagName('content')[0].firstChild
							if content_t is not None:
								content = content_t.data
							fonts = text.getElementsByTagName('font')
							print('content=',content)
							if fonts.length > 0 and content.lower().replace("!","") == u'fret':
								is_aln = False
								if content.lower() == (u'fret!'):
									is_aln = True
								color = fonts[0].getAttribute('color')
								height = int(fonts[0].getAttribute('height'))
								fretInfoStaves.append((color, height, staffName, staffID, is_aln))
			staffID = staffID + 1
	if len(fretInfoStaves) > 1:
		for fretInfoStaff in fretInfoStaves:
			color, height, staffName, staffID, is_aln = fretInfoStaff
			if (selected_staff == staffID):
				return (color, height, staffName, is_aln)

	elif len(fretInfoStaves) == 1:
		color, height, staffName, staffID, is_aln = fretInfoStaves[0]
		return (color, height, staffName, is_aln)

	return (u'', 0, u'-', False)

# Count Global and Local Fret Indexes
def getFretIndexCount(score):
	numVoices = 0
	numStaves = 0
	numGFI = 0
	numLFI = 0
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:	
			staffName = staff.getAttribute('layout')
			if staffName == fretStaffName:
				gfiX = getGlobalFretIndexPositions(staff, countOnly = True)
				totalTime = 0.0
				gfi = genGlobalFretIndexPosRanges(gfiX, totalTime, [], countOnly=True)
				print("gfi", gfi)
				if len(gfi) > 0:
					if gfi[0][0] is not None:
						numGFI = numGFI + len(gfi)
				ovoices, voices = getChildren(staff, 'voice')
				vcount = 0
				for voice in voices:
					vcount = vcount + 1
					onoteObjs, noteObjs = getChildren(voice, 'noteObjects')
					for noteObj in noteObjs:
						odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
						for drawObj in drawObjs:
							if isTextCriteriaDrawObj(drawObj, 'localFretIndex'):
								content = getTextContent(drawObj)
								try:
									if len(content) > 0:
										_ = int(content)
										numLFI = numLFI + 1
								except:
									pass
				if hasStaffAlnMark(staff):
					numVoices = max(numVoices, vcount)
				numStaves = numStaves + 1
	return (numGFI, numLFI, numVoices, numStaves)


class Config:
	def __init__(self):
		self.file = ScriptOptions()
		self.dic = {}

	def get(self, varName, defValue):
		value = self.dic.get(varName)
		if value is None:
			value = defValue	
			self.set(varName, defValue)
		return value

	def set(self, varName, value):
		self.dic.update({varName: value})

	def load(self):
		try:
			self.dic = self.file.get()
			for key in self.dic.keys():
				value = self.dic.get(key)
				self.dic.update({key: bool(int(value))})
			print("loaded", self.dic)
		except:
			print("Could not open configuration file",self.file)

	def save(self):
		try:
			self.file.set(self.dic)
		except:
			print("Could not save configuration file",self.file)

config = Config()
 
def msgFretMissingColor():
	messageBox('Achtung', 'Der Fret-Index muss farblich gekennzeichnet sein (Standard: Blau).')
	
isAln = False
# Analyze Phase
class ScoreAnalyze(ScoreChange):
	def changeScore(self, score):
		global estFretIndexFontColor
		global estFretIndexFontSize
		global fretStaffName
		global numVoices
		global numStaves
		global numGFI
		global numLFI
		global isAln
		estFretIndexFontColor, estFretIndexFontSize, fretStaffName, isAln = getFretColorAndSize(score)
		numGFI, numLFI, numVoices, numStaves = getFretIndexCount(score)
		if estFretIndexFontSize > 0 and (estFretIndexFontColor == '000000' or estFretIndexFontColor == ''):
			msgFretMissingColor()
	
# Plugin Processing Phase
class ScoreChange(ScoreChange):
	def changeScore(self, score):
		global doc
		global estFretIndexFontColor
		global estFretIndexFontSize
		global fretStaffName
		global setActive
		global setHide
		global setSmall
		global setSuppress
		global setCollision
		doc = score.parentNode
		aLNToOrg(score)
		if setActive:
			orgToALN(score, not setActive)

# Plugin Dialog
def dialog():
	global isAln
	global setActive
	global setHide
	global setSmall
	global setSharp
	global setSuppress
	global setCollision

	global checkFIA
	global checkHide
	global checkSmall
	global checkSuppress
	global checkSharp
	global checkCollision
	global checkReset
	global config
	config.load()
	setActive = True
	setHide = config.get('setHide', True)
	setSuppress = config.get('setSuppress', True)
	setSharp = config.get('setSharp', True)
	setSmall = config.get('setSmall', False)
	setCollision = config.get('setCollision', True)

	wdt = 40

	fretIndexColor = Label('Fret-Index Font-Farbe', width=wdt/2)
	fretIndexHeight = Label('Fret-Index Font-Größe', width=wdt/2)
	fretIndexStaffName = Label('Fret-Index Notenzeilenname', width=wdt/2)
	numGlobalFI = Label('Anzahl globale Fret-Indexe', width=wdt/2)
	numLocalFI = Label('Anzahl lokale Fret-Indexe', width=wdt/2)
	textIsParsed = Label('Fret-Index aktuell aktiv', width=wdt/2)
	vBoxFretIndexLabels = VBox([fretIndexColor, fretIndexHeight, fretIndexStaffName, numGlobalFI, numLocalFI, textIsParsed])

	fColor = 'FRET FEHLT!'
	fSize = 'FRET FEHLT!'
	printedNumGFI = '-'
	printedNumLFI = '-'
	isConv = '-'
	printedNumStaves = '-'
	printedNumVoices = '-'
	if estFretIndexFontColor != '':
		fColor = '#'+estFretIndexFontColor
		fSize = str(estFretIndexFontSize)+' px'
		printedNumGFI = str(numGFI)
		printedNumLFI = str(numLFI)
		printedNumStaves = str(numStaves)
		printedNumVoices = str(numVoices)
		if isAln:
			isConv = "Ja"
		else:
			isConv = "Nein"
	fretIndexColorValue = Label(': '+fColor, width=wdt/2-1)
	fretIndexHeightValue = Label(': '+fSize, width=wdt/2-1)
	fretIndexStaffNameValue = Label(': '+fretStaffName, width=wdt/2-1)
	numGlobalFIValue = Label(': '+printedNumGFI, width=wdt/2-1)
	numLocalFIValue = Label(': '+printedNumLFI, width=wdt/2-1)
	textIsParsedValue = Label(': '+isConv, width=wdt/2-1)
	vBoxFretIndexValues = VBox([fretIndexColorValue, fretIndexHeightValue, fretIndexStaffNameValue, numGlobalFIValue, numLocalFIValue, textIsParsedValue])

	propertiesBox = HBox([vBoxFretIndexLabels, vBoxFretIndexValues], text = 'Ermittelte Fret-Eigenschaften')

	labelStaffs = Label('Anzahl aLN-Notenzeilen'+printedNumStaves, width=wdt/2)
	labelVoices = Label('Anzahl Stimmen'+printedNumVoices, width=wdt/2)
	vBoxStaffVoiceLabels = VBox([labelStaffs, labelVoices])

	labelStaffsValue = Label(': '+printedNumStaves, width=wdt/2-1)
	labelVoicesValue = Label(': '+printedNumVoices, width=wdt/2-1)
	vBoxStaffVoiceValues = VBox([labelStaffsValue, labelVoicesValue])

	staffPropBox = HBox([vBoxStaffVoiceLabels, vBoxStaffVoiceValues], text = 'Ermittelte Notenzeilen-Eigenschaften')

	checkHide = CheckBox('Stimme unsichtbar', value=setHide, width=wdt)
	checkSmall = CheckBox('Kleine Noten', value=setSmall, width=wdt)
	checkSuppress = CheckBox('Vorzeichen (♯,  ♭) unterdrücken', value=setSuppress, width=wdt)
	checkSharp = CheckBox('Vorzeichen ♯ bevorzugen', value=setSharp, width=wdt)
	checkCollision = CheckBox('Auto-Kollisionsvermeidung abschalten', value=setCollision, width=wdt)
	convertOptionsBoxV = VBox([checkHide, checkSmall, checkSuppress, checkSharp, checkCollision])
	convertOptionsBox = HBox([convertOptionsBoxV], text = 'Einstellungen für konvertierte Stimme')

	checkReset = CheckBox('EINSTELLUNGEN ZURÜCKSETZEN', value=False, width=wdt)
	checkFIA = CheckBox('Fret-Index Stimme(n) erzeugen (oder exist.  löschen)', value=setActive, width=wdt)
	pluginBoxV= VBox([checkReset, checkFIA])
	pluginBox = HBox([pluginBoxV], text = 'Plugin-Anwendung')

	vBox = VBox([propertiesBox, staffPropBox, convertOptionsBox, pluginBox], padding=12)
	dlg = Dialog('acaLead', vBox)
	return dlg

# Plugin Main Procedure - Analyze, Dialog, Processing
if activeScore():
	tempInput = tempfile.mktemp('.capx')
	tempOutput = tempfile.mktemp('.capx')
	activeScore().write(tempInput)
	ScoreAnalyze(tempInput, tempOutput)
	os.remove(tempInput)
	os.remove(tempOutput)

	dialogPass = False
	while not dialogPass:
		dialogOkClicked = dialog().run()
		setReset = checkReset.value()
		if setReset and dialogOkClicked:
			config.dic = {}
			dialogStatus = False
			dialogPass = False
			config.save()
		else:
			dialogPass = True

	if dialogOkClicked:
		setActive = checkFIA.value()
		setHide = checkHide.value()
		setSmall = checkSmall.value()
		setSuppress = checkSuppress.value()
		setSharp = checkSharp.value()
		setCollision = checkCollision.value()
		config.set('setHide', setHide)
		config.set('setSuppress', setSuppress)
		config.set('setSharp', setSharp)
		config.set('setSmall', setSmall)
		config.set('setCollision', setCollision)
		config.save()
		activeScore().registerUndo("acaLead")
		if estFretIndexFontSize == 0 and not (not setActive and estFretIndexFontColor == '' and setFingeringMarker):
			messageBox("Fret Index", "Bitte zuerst das 'Fret'-Schlüsselwort platzieren (siehe Plugin-Beschreibung)", img=3)
		else:
			tempInput = tempfile.mktemp('.capx')
			tempOutput = tempfile.mktemp('.capx')
			activeScore().write(tempInput)
			ScoreChange(tempInput, tempOutput)
			activeScore().read(tempOutput)
			os.remove(tempInput)
			os.remove(tempOutput)


