# -*- coding: UTF-8 -*-
""" capellaScript -- (C) 2025 Ken Haiker, acaMusic (www.acamusic.de)
                         Implementation: Brian Schueler
>>> aC.Converter
    Rev 1.2.27 (24.07.2026) --- Plugin allgemein für Musiker
    ||
    Der aC.Converter wandelt klassische absolute 
    Akkordsymbole (Chords) in generische relative Stufennummern (Steps, nach 
	der Methode von acaChords Notation) um, und umgekehrt.
    ||
    Für beide Konvertierungen kann die deutsche oder angelsächsische
    Schreibweise gewählt werden. Der Unterschied liegt in der Darstellung
    der Moll-Akkorde bzw. Moll-Stufennummern entweder als Kleinbuchstabe
    oder Großbuchstabe mit einem zusätzlichen m (z.B. c oder Cm bzw. i oder
    Im).
    ||
    -------------   www.acaMusic.de   -------------
    ||
    Auf der Homepage gibt es ein ausführliches Handbuch zum Plugin.
	||
	Dem Plugin derzeit bekannte Akkorde (chords):
	|6, 7, maj7, 7b5, 7#5, maj7#5, 7add9, maj7add9, 7add#11, maj7add#11, 6add9, add9, dim, dim7, 
	sus2, 7sus2, sus4, 7sus4, aug, 5 sowie Slash-Akkorde mit veränderten Bass-Noten
<<<

History:  Jul 2025 - Erste Version

"""

import pprint
import re
import tempfile
from caplib.capDOM import ScoreChange, childElements
from xml.dom.minidom import NodeList, Node, Element

sys.stdout = open(tempfile.gettempdir()+'capella-cs-converter.log', 'w')

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 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)

def addNewElementNode(el,tagName):
	# add new Node with tagName "tagName" to el
	global doc
	newChild = doc.createElement(tagName)
	el.appendChild(newChild)
	return newChild

# Get the base child object and the child list of an object
def getChildren(el, name, forcedPlural = None):
        if name == 'staff':
                namePlural = 'staves'
        elif name == 'drawObj':
                namePlural = 'drawObjects'
        else:
                namePlural = name+'s'
	if forcedPlural is not None:
		namePlural = forcedPlural
        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)

# 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
                base = baseT.split('/')
                if len(base) == 2:
                        value = float(base[0])/float(base[1])
                else:
                        value = float(base[0])

                # Tuplet Note
                tuplet = duration[0].gotoChild('tuplet', False)
                if tuplet is not None:
                        count = int(tuplet.getAttribute('count'))
                        if count == 3:
                                value = value * 2.0 / 3.0

                # Dotted Note
                dots = duration[0].getAttribute('dots')
                if len(dots) > 0:
                        for i in range(0,int(dots)):
                                value = value + value/2.0
                return value
        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 getChordSymProps(noteObj):
	odrawObjects, drawObjects = getChildren(noteObj, "drawObj") 
	coll_x = []
	coll_y = []
	is_above = True
	is_auto = False

	for drawObj in drawObjects:     
		chord = getGroup(drawObj)
		trans = drawObj.gotoChild('transposable', False)
		group = drawObj.gotoChild("group", False)
		validChord = False
		if group is not None or trans is not None:
			chd = chord
			if type(chd) is list:
				chd = chd[0]
			validChord = acaChords.check_chord(chd, setGermanH)
		if trans is not None and validChord:
			basic = drawObj.gotoChild('basic')
			if basic is not None:
				yPlacement = basic.getAttribute('yPlacement')
				placement = basic.getAttribute('placement')
				if yPlacement == 'below':
					is_above = False
				if placement == 'auto':
					is_auto = True
			t_drawObjs = trans.getElementsByTagName('drawObj')
			for t_drawObj in t_drawObjs:
				t_group = t_drawObj.gotoChild('group')
				if t_group is not None:
					t_g_drawObjs = t_group.getElementsByTagName('drawObj')
					objID = 0
					for t_g_drawObj in t_g_drawObjs:
						text = t_g_drawObj.gotoChild('text')
						t_x = text.getAttribute('x')
						t_y = text.getAttribute('y')
						if t_x is not None and t_y is not None and objID == 0:
							coll_x.append(float(t_x))
							coll_y.append(float(t_y))
						objID = objID + 1
		elif group is not None and validChord:
			g_drawObjs = group.getElementsByTagName('drawObj')
			basic = drawObj.gotoChild('basic')
			if basic is not None:
				yPlacement = basic.getAttribute('yPlacement')
				placement = basic.getAttribute('placement')
				if yPlacement == 'below':
					is_above = False
				if placement == 'auto':
					is_auto = True
			objID = 0
			for g_drawObj in g_drawObjs:
				text = g_drawObj.gotoChild('text')
				t_x = text.getAttribute('x')
				t_y = text.getAttribute('y')
				if t_x is not None and t_y is not None:
					if objID == 0:
						coll_x.append(float(t_x))
						coll_y.append(float(t_y))
					objID = objID + 1
	
	if len(coll_x) > 0 and len(coll_y) > 0:
		return sum(coll_x)/len(coll_x), sum(coll_y)/len(coll_y), is_above, is_auto
	return 0, 0, is_above, is_auto

def getChordSyms(noteObj):
	retval = []
	odrawObjects, drawObjects = getChildren(noteObj, "drawObj")
	for drawObj in drawObjects:
		chord = getGroup(drawObj)
		trans = drawObj.gotoChild('transposable', False)
		group = drawObj.gotoChild("group", False)
		if group is not None or trans is not None:
			chd = chord
			if type(chd) is list:
				chd = chd[0]
			validChord = acaChords.check_chord(chd, setGermanH)
		if len(chord) > 0 and (trans is not None or group is not None) and validChord:
			retval.append(str.join("",chord))
	return retval

def getChordSym(noteObj):
	chord = []
	odrawObjects, drawObjects = getChildren(noteObj, "drawObj")
	for drawObj in drawObjects:
		chord = getGroup(drawObj)
		trans = drawObj.gotoChild('transposable', False)
		group = drawObj.gotoChild("group", False)
		validChord = False
		if group is not None or trans is not None:
			chd = chord
			if type(chd) is list and len(chd) > 0:
				chd = chd[0]
			validChord = acaChords.check_chord(chd, setGermanH)
		if len(chord) > 0 and (trans is not None or group is not None) and validChord:
			return chord
	return ''

def getGroup(drawObj):
	retval = []
	try:
		trans = drawObj.gotoChild('transposable', False)
		group = drawObj.gotoChild("group", False)

		if trans is not None:
			print("getGroup - has trans")
			trans = getTransposable(drawObj)
			print("getGroup - trans", str.join('', trans))
			retval.extend(trans)
		elif group is not None:
			print("getGroup - has group")
			xdrawObjs = childElements(group, "drawObj")
			tempTrans = []
			tempRest = []
			for xdrawObj in xdrawObjs:
				trans = getTransposable(xdrawObj)
				# Force transposables becoming first
				if len(trans) > 0:
					print('getGroup - detected trans:', trans)
					tempTrans.extend(trans)
				else:
					tempRest.extend(getGroup(xdrawObj))
			retval.extend(tempTrans)
			retval.extend(tempRest)
		else:
			print("getGroup - no group")
			text = drawObj.gotoChild("text", False)
			print("getGroup - drawObj:", drawObj)
			print("getGroup - text:", text)
			if text is not None:
				print("getGroup - has text")
				height = 0
				font = text.gotoChild('font', False)
				if font is not None:
					height = font.getAttribute('height')
				content = text.getElementsByTagName('content')
				if content is not None and len(content) > 0:
					print("getGroup - content", content)
					data = content[0].firstChild.data
					if len(data) > 0:
						if data[0] == 'S':
							data = data.replace('S','#')
						elif data[0] == 'Q':
							data = data.replace('Q','b')
						elif data == '/':
							if height > 10:
								data = '<altslash>'
					data = data.replace(" ","")
					print("getGroup - data", data)
					retval.append(data)
	except:
		print("getGroup - No chord symbol found")
	print("getGroup - chord", str.join('', retval))
	return retval

def getTransposable(drawObj):
	retval = []
	try:
		transposable = gotoChild(drawObj, 'transposable', False)
		if transposable is not None:
			print("getTransposable - has transposable")
			base = transposable.getAttribute('base')
			transDrawObjects = childElements(transposable,"drawObj")
			for transDrawObj in transDrawObjects:
				transBase = transDrawObj.getAttribute('base')
				if base is not None and transBase == base:
					retval.extend(getGroup(transDrawObj))
		else:
			print("getTransposable - no transposable")
	except:
		print("getTransposable - No trans chord symbol found")
	return retval

def getChordType(chord):
	valid = acaChords.check_chord(chord, False) or acaChords.check_chord(chord, True)
	if valid and len(chord) > 0:
		sfx = acaChords.get_chord_suffix(acaChords.chord_angsax(chord))
		style = ''
		if sfx.startswith('m') and acaChords.is_minor(acaChords.chord_angsax(chord)):
			style = 'angSax'
		if chord[0] in ['i', 'v']:
			return ['generic-rom', 'german']
		elif chord[0] in ['I', 'V']:
			return ['generic-rom', style]
		elif ord(chord[0]) in range(ord('1'), ord('7')):
			return ['generic-dec', style]
		elif ord(chord[0]) in range(ord('a'), ord('h')):
			return ['classic', 'german']
		elif ord(chord[0]) in range(ord('A'), ord('H')):
			return ['classic', style]
	return ['', '']

def removeChordSym(chord):
	odrawObjects, drawObjects = getChildren(chord, 'drawObj')
	if len(drawObjects) == 0:
		return

	removeList = []
	for drawObj in drawObjects:
		transposable = drawObj.gotoChild('transposable', False)
		group = drawObj.gotoChild('group', False)
		if transposable is not None:
			removeList.append(drawObj)
		elif group is not None:
			removeList.append(drawObj)
	for elem in removeList:
		drawObjects.remove(elem)
	setChildren(odrawObjects, drawObjects, 'drawObj')

def setSimpleChordSym(chord, x=0.0, y=0.0, above = True, autoPlace = False, root='', deri='', bass=''):
	drawObjs = chord.gotoChild('drawObjects', True)
	newDrawObj = doc.createElement('drawObj')
	basic = newDrawObj.gotoChild('basic', True)
	if autoPlace:
		basic.setAttribute('placement', 'auto')
	if above:
		yPlacement = 'above'
	else:
		yPlacement = 'below'
	basic.setAttribute('yPlacement', yPlacement)
	basic.setAttribute('placementHint', 'chordSymbol')
	group = newDrawObj.gotoChild('group', True)
	setGroupText(group, x, y, root, deri, bass)
	drawObjs.appendChild(newDrawObj)

def setTransposableChordSym(chord, x=0.0, y=0.0, above = True, autoPlace = False, root='', deri='', bass=''):
	t_bases = [ 'Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#' ]
	german_minor = False
	root_extra = ''
	root_split = root.split('[')
	if len(root_split) > 1:
		root_extra = '['+root_split[1]
	if len(root_split) > 0:
		root = root_split[0]
	if len(root) > 0:
		if root[0] in ['a', 'h', 'b', 'c', 'd', 'e', 'f', 'g' ]:
			german_minor = True
			root = root[0].upper() + root[1:]
			root = root + 'm'
	if setGermanH:
		if root.startswith('B'):
			root = 'Bb' + root[1:]
		elif root.startswith('H'):
			root = 'B' + root[1:]
	is_minor = False
	if root.endswith('m'):
		is_minor = True
	drawObjects = chord.gotoChild('drawObjects', True)
	newEl = doc.createElement('drawObj')
	basic = newEl.gotoChild('basic', True)
	if autoPlace:
		basic.setAttribute('placement', 'auto')
	if above:
		yPlacement = 'above'
	else:
		yPlacement = 'below'
	basic.setAttribute('yPlacement', yPlacement)
	basic.setAttribute('placementHint', 'chordSymbol')
	transposable = newEl.gotoChild('transposable', True)
	base = acaChords.get_chord_prefix(root)
	transposable.setAttribute('base', base)
	for t_base in t_bases:
		out_base = t_base
		t_base_pitch = acaChords.midiPitch(t_base+'2')
		c_pitch = acaChords.midiPitch('C2')
		offset = t_base_pitch - c_pitch
		if setGermanH:
			if out_base == 'Bb':
				out_base = 'B'
			elif out_base == 'B':
				out_base = 'H'
			elif out_base == 'B#':
				out_base = 'H#'
		if is_minor:
			if german_minor:
				out_base = out_base.lower()
			else:
				out_base = out_base + 'm'
		out_base = out_base + root_extra
		newDrawObj = doc.createElement('drawObj')
		newDrawObj.setAttribute('base', t_base)
		basic = newDrawObj.gotoChild('basic', True)
		if autoPlace:
			basic.setAttribute('placement', 'auto')
		basic.setAttribute('yPlacement', yPlacement)
		basic.setAttribute('placementHint', 'chordSymbol')
		group = newDrawObj.gotoChild('group', True)
		try:
			bass_t = acaChords.get_transposed_chord_name(bass, offset, True)
			if bass_t is not None and bass_t != '':
				bass = bass_t
		except:
			pass
		setGroupText(group, x, y, out_base, deri, bass)
		transposable.appendChild(newDrawObj)
	drawObjects.appendChild(newEl)

def setGroupText(group, x=0.0, y=0.0, text_root = '', text_deri = '', text_bass = ''):
	x, y = setText(group, text_root, 10, x, y)
	if text_deri != '':
		y = y - 1.0
		x, y = setText(group, text_deri, 7, x, y)
	if text_bass != '':
		text_bass = text_bass.replace('/', '')
		y = y + 2.0
		x = x - 0.6
		x, y = setText(group, '/', 18, x, y)
		x = x - 1.5
		x, y = setText(group, text_bass, 10, x, y)
		

def setText(group, text, font_size, x, y):
	text_elems = []
	part = ''
	counter = 0
	for ch in text:
		if ch in ['b', '#'] and counter > 0:
			text_elems.append(part)
			text_elems.append(ch)
			part = ''
		else:
			part = part + ch
		counter = counter + 1
	text_elems.append(part)

	counter = 0
	for text_elem in text_elems:
		fact_extra = 1.0
		size_fact = 1.0
		x_offs = 0
		y_offs = 0
		if text_elem not in ['b', '#'] or counter == 0:
			font_func = setStandardFont
		else:
			text_elem = text_elem.replace('b', 'Q').replace('#', 'S')
			fact_extra = 0.3
			x_offs = -0.05 * font_size / 7.0
			y_offs = -0.25 * font_size / 7.0
			size_fact = 1.0
			font_func = setCapellaFont
		setTextElement(group, text_elem, font_size*size_fact, x+x_offs, y+y_offs, font_func)
		x = x + measureText(text_elem) * font_size*size_fact * fact_extra / 6.0
		counter = counter + 1
	return x, y

def measureText(text):
	letters_s = ['i', 'I', 'l', '1', '*', "'", '!', '[', ']']
	letters_l = ['M', 'm', 'W', 'w', 'V', 'C', 'D', 'E', 'F', 'G', 'H', 'K', 'N', 'O' , 'Q', 'S', 'U', 'X', 'Z']
	width = 0
	for letter in text:
		if letter in letters_s:
			# Thin letters
			width = width + 0.5
		elif letter in letters_l:
			# Thick letters
			width = width + 1.1
		else:
			# Normal sized letters
			width = width + 0.85
	return width

def setTextElement(group, text, font_size, x, y, font_func):
	newEl = doc.createElement('drawObj')
	textEl = newEl.gotoChild('text', True)
	textEl.setAttribute('x', str(x))
	textEl.setAttribute('y', str(y))
	font_func(textEl, font_size)
	content = addNewElementNode(textEl, 'content')
	textNode = doc.createTextNode(text)
	content.appendChild(textNode)	
	group.appendChild(newEl)

def setStandardFont(text, height = 10):
	font = text.gotoChild('font', True)
	font.setAttribute('face', 'Times New Roman')
	font.setAttribute('height', str(height))

def setCapellaFont(text, height = 12):
	font = text.gotoChild('font', True)
	font.setAttribute('face', 'capella3')
	font.setAttribute('height', str(height))
	font.setAttribute('charSet', '2')
	font.setAttribute('pitchAndFamily', '2')

class ChordType:
	def __init__(self, tokenList, major, minor, group, coeff, allow_inversions):
		self.tokenList = tokenList
		self.major = major
		self.minor = minor
		self.group = group
		self.coeff = coeff
		self.allow_inversions = allow_inversions

	def __repr__(self):
		return 'Chord: '+str(self.tokenList)+'), coeff: '+str(self.coeff)+', inv allowed: '+str(self.allow_inversions)+'\n'

class AcaChords:
	def __init__(self):
		self.min_tone = 'C2'
		self.max_tone = 'B3'
		self.octave = 2
		self.chord_types = [
			# Chords based on a Major triad
			ChordType([""],                                               True,  False, "maj", [0, 4, 3],       True),  # "Major"
			ChordType(["6"],                                              True,  False, "maj", [0, 4, 3, 2],    False), # "Sixth"
			ChordType(["7"],                                              True,  False, "maj", [0, 4, 3, 3],    True),  # "Seventh"
			ChordType(["maj7"],                                           True,  False, "maj", [0, 4, 3, 4],    True),  # "Major seventh"
			ChordType(["7(b5)", "7/b5", "7b5"],                           False, False, "b5", [0, 4, 2, 4],    False), # "Seven flat five"
			ChordType(["7(#5)", "7/#5", "7#5"],                           False, False, "#5", [0, 4, 4, 2],    False), # "Seven sharp five"
			ChordType(["maj7(#5)", "maj7/#5", "maj7#5"],                  False, False, "#5", [0, 4, 4, 3],    False), # "Major seventh sharp five"
			ChordType(["7(9)", "7/9", "9", "7add9"],                      True,  False, "maj", [0, 4, 3, 3, 4], False), # "Seventh add ninth"
			ChordType(["maj7(9)", "maj7/9", "maj9", "maj7add9"],          True,  False, "maj", [0, 4, 3, 4, 3], False), # "Major seventh add ninth"
			ChordType(["7(#11)", "7/#11", "7#11", "7add#11"],             True,  False, "maj", [0, 4, 3, 3, 8], True),  # "Seventh augmented eleventh"
			ChordType(["maj7(#11)", "maj7/#11", "maj7#11", "maj7add#11"], True,  False, "maj", [0, 4, 3, 4, 7], True),  # "Major seventh augmented eleventh"
			ChordType(["6(9)", "6/9", "6add9"],                           True,  False, "maj", [0, 4, 3, 2, 5], False), # "Six-nine"
			ChordType(["(9)", "add9"],                                    True,  False, "maj", [0, 4, 3, 7],    True),  # "Add ninth"
			# Chords based on a Minor triad
			ChordType(["m"],                                              False, True,  "min", [0, 3, 4],       True),  # "Minor"
			ChordType(["m6"],                                             False, True,  "min", [0, 3, 4, 2],    True),  # "(Minor) sixth"
			ChordType(["m7"],                                             False, True,  "min", [0, 3, 4, 3],    False), # "(Minor) seventh"
			ChordType(["mmaj7"],                                          False, True,  "min", [0, 3, 4, 4],    False), # "(Minor) major seventh"
			ChordType(["m7(b5)", "m7/b5", "m7b5"],                        False, False, "b5", [0, 3, 3, 4],    False), # "Half diminished seventh"
			ChordType(["m7(9)", "m7/9", "m9", "m7add9"],                  False, True,  "min", [0, 3, 4, 3, 4], True),  # "(Minor) seventh add ninth"
			ChordType(["m7(11)", "m7/11", "m11", "m7add11"],              False, True,  "min", [0, 3, 4, 3, 7], False), # "(Minor) seventh add eleventh"
			ChordType(["m6(9)", "m6/9", "m6add9"],                        False, True,  "min", [0, 3, 4, 2, 5], False), # "(Minor) six-nine"
			ChordType(["m(9)", "madd9"],                                  False, True,  "min", [0, 3, 4, 7],    True),  # "(Minor) add ninth"
			# Diminished Chords (minor Chords with deminished fifths)
			ChordType(["dim", "o", "°", "mb5"],                           False, False, "dim", [0, 3, 3],       False), # "Diminished"
			ChordType(["dim7", "o7", "°7"],                               False, False, "dim", [0, 3, 3, 3],    False), # "Diminished seventh"
			# Suspended Chords (3rd suspended and replaced by a 4th or 2nd)
			ChordType(["sus2"],                                           False, False, "sus2", [0, 2, 5],       False), # "Suspended second"
			ChordType(["7sus2", "sus27", "sus2dom7"],                     False, False, "sus2", [0, 2, 5, 3],    False), # "Seventh Suspended second"
			ChordType(["sus4", "sus"],                                    False, False, "sus4", [0, 5, 2],       False), # "Suspended fourth"
			ChordType(["7sus4", "sus47", "sus7", "sus4dom7"],             False, False, "sus4", [0, 5, 2, 3],    False), # "Seventh suspended fourth"
			# Augmented Chords (Major Chords with augmented fifths)
			ChordType(["aug", "+"],                                       False, False, "aug", [0, 4, 4],       False), # "Augmented fifth"
			# Power Chords (missing 3rd)
			ChordType(["5", "no3"],                                       False, False, "pow", [0, 7],          False), # "Power"
		]
		self.key_notes = "CDEFGAB"
		self.key_note_tone_number = (1, 3, 5, 6, 8, 10, 12)
		self.use_inversion = True
		self.rebuild_chord_maps()
	
	def rebuild_chord_maps(self):
		self.chords_by_token_map = {}
		self.chords_by_step_coeff_map = {}

		for chord_type in self.chord_types:
			for token in chord_type.tokenList:
				self.chords_by_token_map.update({'x'+token: chord_type})
				for prio in range(0,len(chord_type.coeff)):
					if chord_type.allow_inversions or prio == 0:
						path = ""
						coeff, tonic_index = self.get_inversion(chord_type.coeff, prio)
						for coeff_elem in coeff:
							path = path + str(coeff_elem)
						path = path + str(prio)
						path = path + str(tonic_index)
						self.chords_by_step_coeff_map.update({path: chord_type})

		print(self.chords_by_token_map)
		print(self.chords_by_step_coeff_map)

	def get_token(self, s):
		if s[0] == '#':
			return ('sharp', 1)
		elif s[0] == 'b':
			return ('flat', 1)
		elif s[0] >= 'A' and s[0] <= 'G':
			return ('base_tone', 1)
		elif len(s) > 1:
			if s[0] == '-':
				return ('unknown', 2)
		return ('unknown', 1)

	def get_base_tone(self, s):
		try:
			index = self.key_notes.index(s)
			if index >= 0:
				return self.key_note_tone_numver[index]
		except:
			pass
		return 0

	def aligned_tone_num(self, tone_num):
		tn = tone_num
		while tn <= 0:
			tn = tn + 12
		while tn > 12:
			tn = tn - 12
		return tn

	def has_sharps_circle(self, base_tone, minor, double_def_sharp = True):
		circleOfFifthsMajSharp = [ 1, 3, 5, 8, 10, 12 ]
		circleOfFifthsMajFlat  = [ 2, 4, 6, 9, 11 ]
		circleOfFifthsMinSharp = [ 2, 5, 7, 9, 10, 12 ]
		circleOfFifthsMinFlat  = [ 1, 3, 6, 8, 11 ]
		base_tone = self.aligned_tone_num(base_tone)
		if minor:
			if base_tone in circleOfFifthsMinSharp:
				return True
			elif base_tone in circleOfFifthsMinFlat:
				return False
			else:
				return double_def_sharp
		else:
			if base_tone in circleOfFifthsMajSharp:
				return True
			elif base_tone in circleOfFifthsMajFlat:
				return False
			else:
				return double_def_sharp

	def has_sharps(self, tone, minor, double_def_sharp = True):
		major_chords_sharp = (1, 3, 5, 8, 10, 12)
		minor_chords_sharp = (2, 5, 7, 9, 10, 12)
		tone_num = self.aligned_tone_num(tone)
		if minor:
			for i in range(0, len(minor_chords_sharp)):
				if minor_chords_sharp[i] == tone_num:
					return True
			if tone_num == 4:
				return double_def_sharp   # 4 ^= Eb/D# (inverted)
		else:
			for i in range(0, len(major_chords_sharp)):
				if major_chords_sharp[i] == tone_num:
					return True
			if tone_num == 7:
				return double_def_sharp   # 7 ^= F#/Gb
		return False

	# Return: coeff, tonic_index
	def get_inversion(self, coeff, inv_num):
		tone_vals = []
		tone_val_sum = 0
		num_tones = len(coeff)
		for i in range(0, num_tones):
			tone_val_sum = tone_val_sum + coeff[i]
			tone_vals.append(tone_val_sum)
		for i in range(0, min(inv_num, num_tones)):
			tone_vals[i] = tone_vals[i] + 12
		tonic_val = tone_vals[0]
		tone_vals.sort()
		tonic_i = tone_vals.index(tonic_val)
		new_tone_steps = [0]
		for i in range(1,len(tone_vals)):
			new_tone_steps.append(tone_vals[i] - tone_vals[i-1])
		return (new_tone_steps, tonic_i)

	def chord_to_tones_basstone(self, chord, basstone, octave, transpose = 0):
		retval = []
		print('chord_to_tones_basstone - chord:', chord)
		print('chord_to_tones_basstone - bass:', basstone)
		chord_type = self.get_chord_type(chord)
		print('chord_to_tones_basstone - type:', chord_type)
		coeff = chord_type.coeff
		print('chord_to_tones_basstone - coeff:', coeff)
		tones_n = self.chord_to_tones(chord, octave)
		tones = []
		for tone in tones_n:
			tones.append(re.sub('[0-9]','',tone))
		print('chord_to_tones_basstone - tones:', tones)
		bass_index = 0
		if tones.count(basstone) > 0:
			bass_index = tones.index(basstone)
		print('chord_to_tones_basstone - bass index:', bass_index)
		coefficients = []
		tones = []
		for i in range(0, len(coeff)):
			tone_steps, tonic_idx = self.get_inversion(coeff, i)
			print('chord_to_tones_basstone - ts, ti:', tone_steps, tonic_idx)
			pitch = 0
			for i in range(0, tonic_idx+1):
				pitch = pitch - tone_steps[i]

			tones = []
			for step in tone_steps:
				pitch = pitch + step
				base_tone = self.get_chord_prefix(chord)
				ntone = self.midiPitch(base_tone+str(octave))+pitch
				tone = self.pitchFromMidi(ntone)
				tones.append(tone)

			first_tone = re.sub('[0-9]', '', tones[0])
			print('chord_to_tones_basstone - ts:', tones)
			if first_tone == basstone:
				print('chord_to_tones_basstone - returned tones:', tones)
				return tones
			basstone_num = 0
			try:
				basstone_num = int(basstone)
			except:
				pass
			if basstone_num > 0:
				root = self.get_chord_prefix(chord)
				if acaChords.is_minor(chord):
					scale = acaChords.get_minor_key_scale(root)
					print('chord_to_tones_basstone - '+root+'m-scale:', scale)
				else:
					scale = acaChords.get_major_key_scale(root)
					print('chord_to_tones_basstone - '+root+'-scale:', scale)
				try:
					if scale[basstone_num-1] == first_tone:
						print('chord_to_tones_basstone - returned tones:', tones)
						return tones
				except:
					pass
		return self.chord_to_tones(chord, octave, transpose = 0)

	def get_keys_from_circle(self, index):
		major_keys_pos = ['C','G','D','A','E','B','F#' ]
		major_keys_neg = ['C','F','Bb','Eb','Ab','Db','Gb' ]
		minor_keys_pos = ['Am','Em','Bm','F#m','C#m','G#m','D#m' ]
		minor_keys_neg = ['Am','Dm','Gm','Cm','Fm','Bm','Ebm' ]
		while index > 6:
			index = index - 12
		while index < -6:
			index = index + 12
		if index < 0:
			return (major_keys_neg[-index], minor_keys_neg[-index])
		return (major_keys_pos[index], minor_keys_pos[index])

	def force_sign(self, tone_s, sharp = True):
		flats  = ['Db', 'Eb', 'Gb', 'Ab', 'Bb']
		sharps = ['C#', 'D#', 'F#', 'G#', 'A#']
		srcs = flats
		tgts = sharps
		if sharp == False or sharp == 'b':
			srcs = sharps
			tgts = flats

		# in case of a list
		if type(tone_s) is list:
			retval = []
			for tone in tone_s:
				for i in range(0, 5):
					tone = re.sub('^'+srcs[i], tgts[i], tone)	
				retval.append(tone)
			return retval

		# in case of a string
		for i in range(0, 5):
			tone_s = re.sub('^'+srcs[i], tgts[i], tone_s)	
		return tone_s

	def get_major_key_scale(self, root):
		step_widths = [2, 2, 1, 2, 2, 2]
		return self.get_key_scale(root, step_widths)

	def get_minor_key_scale(self, root):
		step_widths = [2, 1, 2, 2, 1, 2]
		return self.get_key_scale(root, step_widths)

	def get_key_scale(self, root, step_widths):
		retval = []
		tone = root
		for step_width in step_widths:
			retval.append(tone)
			tone = self.pitchFromMidi(self.midiPitch(tone+'2')+step_width)
			tone = re.sub('[0-9]', '', tone)
		retval.append(tone)
		return retval

	def get_chord_type_by_token(self, s):
		return self.chords_by_token_map.get('x'+s)

	def get_chord_variants_by_coefficients(self, coeff):
		path = ""
		for coeff_elem in coeff:
			path = path + str(coeff_elem)
		variants = []
		sub_map = { key: val for key, val in self.chords_by_step_coeff_map.items() if key.startswith(path) and len(key) == len(path)+2 }
		for item in sub_map:
			variants.append((int(item[-2]), int(item[-1])))
		return variants

	def get_chord_type_by_coefficients(self, coeff, prio = 0, tonic_index = 0):
		path = ""
		for coeff_elem in coeff:
			path = path + str(coeff_elem)
		path = path + str(prio)
		path = path + str(tonic_index)
		return self.chords_by_step_coeff_map.get(path)

	def get_chord_type(self, chord):
		return self.get_chord_type_by_token(self.get_chord_suffix(chord))

	def is_minor(self, chord):
		chd = self.get_chord_type(chord)
		if chd is not None:
			return chd.minor
		return False

	def is_major(self, chord):
		chd = self.get_chord_type(chord)
		if chd is not None:
			return chd.major
		return False

	def get_octave_shift(self, tone_num, octave):
		shift = 0
		octave = octave + (tone_num - 1) / 12
		tone_num = (tone_num - 1) % 12 + 1

		while tone_num + 12 * octave < self.midiPitch(self.min_tone):
			octave = octave + 1
			shift = shift + 1
		while tone_num + 12 * octave > self.midiPitch(self.max_tone):
			octave = octave - 1
			shift = shift - 1
		return shift

	def get_transposed_chord_name(self, chord, transpose, prefer_sharp):
		if chord == '':
			return ''
		if chord[0].upper() not in ['A','B','C','D','E','F','G']:
			return ''
		tonic = self.get_tonic(chord)
		base_tone = self.midiPitch(tonic+'2')
		new_tone = self.pitchFromMidi(base_tone + transpose, prefer_sharp)
		new_tone_wo_octave = ''
		for s in new_tone:
			if s < '0' or s > '9':
				new_tone_wo_octave = new_tone_wo_octave + s
		return new_tone_wo_octave + self.get_chord_suffix(chord)

	def check_chord(self, chord, german = False):
		global setGermanH
		sc = self.step_to_chord(self.chord_angsax(chord, german), 'C')
		if sc != '':
			return True
		tones = self.chord_to_tones(self.chord_angsax(chord, german),2)
		if len(tones) > 0:
			return True
		return False

	def chord_to_step(self, chord, key, prefer_sharp = True, seven_flat = True):
		try:
			key_range = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
			prefix = self.get_chord_prefix(chord)
			if len(prefix) < 1:
				return ''
			elif prefix[0].upper() not in key_range:
				return ''
			suffix = self.get_chord_suffix(chord)
			key_root = self.get_chord_prefix(key)
			minor = key.endswith('m')
			print('chord_to_step - chord', chord)
			print('chord_to_step - key', key)
			print('chord_to_step - key_root', key_root)
			print('chord_to_step - prefix', prefix)
			print('chord_to_step - suffix', suffix)
			roman_steps = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
			step_mat_flat = ['1', '1#', '2', '2#', '3', '4', '4#', '5', '5#', '6', '7', '7#']
			step_mat_major = ['1', '1#', '2', '2#', '3', '4', '4#', '5', '5#', '6', '6#', '7']
			step_mat_minor = ['1', '1#', '2', '3', '3#', '4', '4#', '5', '6', '6#', '7', '7#']
			key_pitch = acaChords.midiPitch(key_root+'2')
			chord_pitch = acaChords.midiPitch(prefix+'2')
			print('chord_to_step - key_pitch', key_pitch)
			print('chord_to_step - chord_pitch', chord_pitch)
			pitch = chord_pitch - key_pitch
			pitch = pitch % 12
			if minor:
				step = step_mat_minor[pitch]
			elif seven_flat:
				step = step_mat_flat[pitch]
			else:
				step = step_mat_major[pitch]
			step0 = step[0]
			if prefer_sharp == False:
				step0i = int(step0) % 7 + 1
				step = str(step0i) + step[1:].replace('#','b')
			stepi = int(step[0])
			step = roman_steps[stepi-1] + step[1:]
			print('chord_to_step - pitch', pitch)
			print('chord_to_step - step', step)
			return step + suffix
		except:
			pass
		return ''
	
	def step_to_chord(self, step, key, prefer_sharp = True, seven_flat = True):
		try:
			prefix = self.get_chord_prefix(step)
			if len(prefix) < 1:
				return ''
			elif prefix[0].upper() not in ['I', 'V']:
				return ''
			dst_prefix = self.get_chord_prefix(key)
			minor = key.endswith('m')
			print('step_to_chord - step', step)
			print('step_to_chord - key', key)
			print('step_to_chord - prefix', prefix)
			print('step_to_chord - prefix(key)', dst_prefix)
			suffix = self.get_chord_suffix(step)
			print('step_to_chord - suffix', suffix)
			steps = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
			keys_flat = ['C', 'D', 'E', 'F', 'G', 'A', 'Bb']
			keys_root = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
			keys_minor = ['C', 'D', 'D#', 'F', 'G', 'G#', 'A#']
			tune = re.sub('.*([#b])', '\\1', prefix)
			root = re.sub('(.*)[#b]', '\\1', prefix)
			print('step_to_chord - tune', tune)
			print('step_to_chord - prefix (filtered)', prefix)
			if steps.count(root) > 0:
				step_num = steps.index(root)
				if minor:
					key = keys_minor[step_num]
				elif seven_flat:
					key = keys_flat[step_num]
				else:
					key = keys_root[step_num]
				abs_step = self.midiPitch(key+'2')
				abs_ref_step = self.midiPitch(dst_prefix+'2')
				offset = abs_ref_step + abs_step
				if tune == '#':
					offset = offset + 1
				elif tune == 'b':
					offset = offset - 1
				return self.get_transposed_chord_name('C'+suffix, offset, prefer_sharp)
		except:
			pass
		return ''
		
	def chord_angsax(self, chord, parse_german_bflat = False):
		scale = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
		roman_steps = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
		if len(chord) > 0:
			if re.match('[1-7]', chord[0]):
				index = ord(chord[0]) - ord('1')
				chord = roman_steps[index] + chord[1:]

		if parse_german_bflat:
			if len(chord) > 0:
				if chord[0].lower() == 'b':
					chord = chord[0]+'b'+chord[1:]
				elif chord[0] == 'h': 
					chord = 'b'+chord[1:]
				elif chord[0] == 'H': 
					chord = 'B'+chord[1:]

		if len(chord) > 1:
			if chord[0].lower() in scale:
				tone = chord[0]
				sign = ''
				if chord.lower()[1:].startswith('is'):
					sign = '#'
					chord = chord[3:]
				elif chord.lower()[1:].startswith('s'):
					if not chord.lower()[2:].startswith('us'):
						sign = 'b'
						chord = chord[2:]
					else:
						chord = chord[1:]
				elif chord.lower()[1:].startswith('es'):
					sign = 'b'
					chord = chord[3:]
				else:
					chord = chord[1:]
				chord = tone + sign + chord

		if len(chord) > 0:
			if chord[0].islower():
				skip = 0
				if len(chord) > skip and chord[skip].lower() in scale:
					skip = skip + 1
				while len(chord) > skip and chord[skip].lower() in ['i', 'v']:
					skip = skip + 1
					chord = chord[:skip].upper()+chord[skip:]
				if skip > 0:
					if len(chord) > skip:
						if chord[skip] in ['b', '#']:
							skip = skip + 1
					chord = chord[0].upper()+chord[1:skip]+'m'+chord[skip:]
		return chord

	def chord_to_tones(self, chord, octave, transpose = 0):
		print('chord to tones - chord:', chord)
		use_sharp = False
		tones = []
		coefficients = self.get_chord_type_by_token("").coeff   # major chord is default
		print('chord to tones - coeff:', str(coefficients))
		if len(chord) == 0:
			return []
		chord_suffix = self.get_chord_suffix(chord).split('[')[0]
		print('chord to tones - suffix:', chord_suffix)
		chord_type = self.get_chord_type_by_token(chord_suffix)
		print('chord to tones - type:', chord_type)
		if chord_type is not None:
			coefficients = chord_type.coeff
		else:
			return []
		base_tone = None
		try:
			base_tone = self.midiPitch(self.get_chord_prefix(chord)+str(octave))
		except:
			pass
		if base_tone is None:
			return []
		is_minor = chord_type.minor
		if len(chord) >= 2:
			if chord[1] == '#':
				use_sharp = True
		use_sharp = self.has_sharps(base_tone + transpose, is_minor, use_sharp)
		shift = 0
		tone_step = 0
		print('chord to tones - use_inv, allow_inv', self.use_inversion, chord_type.allow_inversions)
		if not self.use_inversion or not chord_type.allow_inversions:
			for i in range(0, len(coefficients)):
				tone_step = tone_step + coefficients[i]
				shift = self.get_octave_shift(base_tone + tone_step + transpose, 0)
				octave = octave + shift

		# generate the tones
		tone_step = 0
		for i in range(0, len(coefficients)):
			tone_step = tone_step + coefficients[i]
			if self.use_inversion and chord_type.allow_inversions:
				shift = self.get_octave_shift(base_tone + tone_step + transpose, 0)
			tones.append(self.pitchFromMidi(base_tone + tone_step + transpose + 12 * (shift) , use_sharp))
		print('chord to tones - returned tones:', tones)
		return tones

	def get_tonic(self, chord):
		tones = self.chord_to_tones(chord, 0, 0)
		if len(tones) > 0:
			return tones[0]
		return ''

	def get_chord_prefix(self, chord):
		try:
			suffix = self.get_chord_suffix(chord)
			suffix_begin = chord.index(suffix)
			prefix = chord
			if len(suffix) > 0:
				prefix = chord[0:suffix_begin]
		except:
			return ''
		return prefix

	def get_chord_suffix(self, chord):
		repeat = True
		chrd = chord
		while repeat:
			repeat = False
			if len(chrd) >= 1:
				if (chrd[0]>='A' and chrd[0]<='G') or (chrd[0]=='b' or  chrd[0]=='#') or (chrd[0]=='I' or  chrd[0]=='V') or (chrd[0]=='i' or  chrd[0]=='v'):
					chrd = chrd[1:]
					repeat = True
		return chrd

	def altered_note(self, tone, root):
		tone_val = self.midiPitch(tone)
		octless_tone_val = (tone_val + 24 - 1) % 12 + 1
		mods = ['bb', 'b', '', '#', '##']
		contains_octave = False
		for s in tone:
			if s >= '0' and s <= '9':
				contains_octave = True

		for mod in mods:
			octless_altered_tone_val = self.midiPitch(root+mod)
			if octless_altered_tone_val == octless_tone_val or (octless_altered_tone_val + 6) % 12 == (octless_tone_val + 6) % 12:
				octave = (tone_val - octless_altered_tone_val) / 12
				if contains_octave:
					return root + mod + str(octave)
				else:
					return root + mod
		return ''

	def int_to_base_tone_no_chord(self, tone_num, sharp):
		tones_flat = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]
		tones_sharp = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "Bb", "B"]
		tone = ''
		while tone_num < 0:
			tone_num = tone_num + 12
		tone_num = tone_num % 12
		if sharp:
			tone = tone + tones_sharp[tone_num]
		else:
			tone = tone + tones_flat[tone_num]
		return tone

	def tones_to_chord(self, tones):
		tone_nums = []
		tone_steps = [0]
		sharps_found = 0
		flats_found = 0
		use_enharp_sharp = False
		for tone in tones:
			tone_nums.append(self.midiPitch(tone))
			if tone[1] == '#':
				sharps_found = sharps_found + 1
			elif tone[1] == 'b':
				flats_found = flats_found + 1
		if sharps_found > flats_found:
			use_enharp_sharp = True
		tone_nums.sort()
		index = 0
		for tone_num in tone_nums:
			if index > 0:
				tone_steps.append(tone_num - tone_nums[index - 1])
			index = index + 1	
		chord_variants = self.get_chord_variants_by_coefficients(tone_steps)
		tonic_index = 99
		chord_type = None

		if chord_variants >= 1:
			for chord_variant in chord_variants:
				v_prio = chord_variant[0]
				v_tonic = chord_variant[1]
				x_chord_type = self.get_chord_type_by_coefficients(tone_steps, v_prio, v_tonic)
				if x_chord_type is not None and v_tonic < tonic_index:
					chord_type = x_chord_type
					tonic_index = v_tonic
		else:
			return ''

		if chord_type is not None:
			chord_suffix = chord_type.tokenList[0]
		else:
			chord_suffix = None
		if chord_suffix is not None:
			tone_num = tone_nums[tonic_index]
			chord_prefix = self.int_to_base_tone_no_chord(tone_num, self.has_sharps(tone_num, self.is_minor('C'+chord_suffix), use_enharp_sharp))
			return chord_prefix + chord_suffix
		return ''

	def midiPitch(self, tone):
		step = 0
		i = self.key_notes.find(tone[0])
		tone = tone[1:]
		if len(tone) < 1:
			return None
		while tone[0] in [u'b', u'#']:
			if tone[0] == 'b':
				step = step - 1
			else:
				step = step + 1
			tone = tone[1:]
		n = (0,2,4,5,7,9,11)[i] + 12 * int(tone[0]) + step
		return n

	def pitchFromMidi(self, n, use_sharp = True):
		octave = int(n)/12
		itone = int(n)%12
		tonesSharp = 'CCDDEFFGGAAB'
		tonesFlat  = 'CDDEEFGGAABB'
		steps      = '010100101010'
		toneSharp = tonesSharp[itone]
		toneFlat  = tonesFlat[itone]
		step = -int(steps[itone])
		tone = toneFlat

		# Use Sharp sign if set
		if use_sharp:
			tone = toneSharp
			step = -step

		sign = ''
		if step < 0:
			sign = 'b'
		elif step > 0:
			sign = '#'
		return tone+sign+str(octave)

def objClefSign(clef = 'treble'):
	newEl = doc.createElement('clefSign')
	newEl.setAttribute('clef', clef)
	return newEl

def addText(object, content = u'', x = 0, y = 0, fontFace = u'Times New Roman', height = 12, charset = 1, tag = ''):
	drawObjs = object.gotoChild('drawObjects', True)
	drawObj = drawObjs.gotoChild('drawObj', True)

	basic = drawObj.gotoChild('basic', True)
	basic.setAttribute('scope', 'brace')
	#basic.setAttribute('placement', 'auto')
	basic.setAttribute('placementHint', 'performanceInstruction')
	basic.setAttribute('tag', tagAcaChordsChord)

	text = drawObj.gotoChild('text', True)
	text.setAttribute('x', str(x))
	text.setAttribute('y', str(y))

	font = text.gotoChild('font', True)
	font.setAttribute('charSet', str(charset))
	font.setAttribute('height', str(height))
	font.setAttribute('face', fontFace)

	contentEl = text.gotoChild('content', True)
	textName = doc.createTextNode(content)
	contentEl.appendChild(textName)

def objChord(tones = [], dura = '1/4', dots = None, tie_begin = False, tie_end = False):
	if tones == []:
		newEl = doc.createElement('rest')
	else:
		newEl = doc.createElement('chord')

	duration = newEl.gotoChild('duration', True)
	duration.setAttribute('base', dura)
	if dots is not None:
		duration.setAttribute('dots', str(dots))
	if tones != []:
		heads = newEl.gotoChild('heads', True)
		for tone in tones:
			head = doc.createElement('head')
			pitch, step = aca2capTone(tone)
			if pitch is not None:
				head.setAttribute('pitch', pitch)
				if step is not None and step != 0:
					alter = doc.createElement('alter')
					alter.setAttribute('step', str(step))
					head.appendChild(alter)
				if tie_begin or tie_end:
					tienode = doc.createElement('tie')
					if tie_begin:
						tienode.setAttribute('begin', 'true')
					if tie_end:
						tienode.setAttribute('end', 'true')
					head.appendChild(tienode)
				heads.appendChild(head)
	return newEl

def cap2acaTone(head, baseOct4):
	pitch = head.getAttribute('pitch')
	alter = head.gotoChild('alter', False)
	step = 0
	if alter is not None:
		step = int(alter.getAttribute('step'))
	tone = pitch
	octave = int(tone[1])
	if baseOct4:
		octave = octave - 1
	if step == 1:
		tone = tone[0] + '#' + str(octave)
	elif step == -1:
		tone = tone[0] + 'b' + str(octave)
	return tone

def aca2capTone(tone):
	step = 0
	if len(tone) < 2:
		return (None, None)
	base = tone[0]
	tone = tone[1:]
	while tone[0] in [u'b', u'#']:
		if tone[0] == 'b':
			step = step - 1
		else:
			step = step + 1
		tone = tone[1:]
	if len(tone) < 1:
		return (None, None)
	octave = tone[0]
	return (base+octave, step)

acaChords = AcaChords()

def getPitches(noteObj):
	retval = []
	oheads, heads = getChildren(noteObj, 'head')
	for head in heads:
		pitch = head.getAttribute('pitch')
		alter = head.gotoChild('alter')
		step = 0
		if alter is not None:
			tstep = alter.getAttribute('step')
			if tstep is not None and tstep != '':
				step = int(tstep)
		stepSigns = [ 'bb', 'b', '', '#', '##' ]
		if pitch is not None:
			retval.append(pitch[0]+stepSigns[step+2]+pitch[1])
	return retval

def getSimpleTexts(noteObj):
	retval = []
	odrawObjs, drawObjs = getChildren(noteObj, 'drawObj')
	
	for drawObj in drawObjs:
		texts  = childElements(drawObj, 'text')
		if len(texts) > 0:
			for text in texts:
				try:
					content = text.getElementsByTagName('content')[0].firstChild.data
					fonts = text.getElementsByTagName('font')
					if fonts.length > 0:
						color = fonts[0].getAttribute('color')
						height = int(fonts[0].getAttribute('height'))
					content = re.sub('\n$', '', content)
					retval.append(content)
				except:
					pass
	return retval

def getStavesByLayout(score, name):
	retval = []
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		newStaves = []
		for staff in staves:
			if staff.getAttribute('layout') == name:
				retval.append(staff)
	return retval

# 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)

def getObjTextObjsByCriteria(noteObj, criteria):
	texts = []
	if noteObj.localName in [ 'chord', 'rest' ]:
		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', False)
		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 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

def getChordSymPosOffset(drawObj):
	xOffs, yOffs = (None, None)
	if drawObj.localName == 'transposable':
		trans = drawObj
	else:
		trans = drawObj.gotoChild('transposable', False)
	if drawObj.localName == 'group':
		group = drawObj
	else:
		group = drawObj.gotoChild("group", False)
	if group is not None:
		groupDrawObjs = childElements(group, 'drawObj')
		for groupDrawObj in groupDrawObjs:
			xOffs, yOffs = getChordSymPosOffset(groupDrawObj)
			if xOffs is not None and yOffs is not None:
				return (xOffs, yOffs)
	elif trans is not None:
		transDrawObjs = childElements(trans, 'drawObj')
		for transDrawObj in transDrawObjs:
			xOffs, yOffs = getChordSymPosOffset(transDrawObj)
			if xOffs is not None and yOffs is not None:
				return (xOffs, yOffs)
	else:
		# Remaining regular text
		if drawObj.localName == 'text':
			text = drawObj
		else:
			text = drawObj.gotoChild('text', False)
		if text is not None:
			x_t = text.getAttribute('x')
			y_t = text.getAttribute('y')
			try:
				xOffs = float(x_t)
				yOffs = float(y_t)
			except:
				pass
	return (xOffs, yOffs)

def moveChordSym(drawObj, xOffs, yOffs):
	if drawObj.localName == 'transposable':
		trans = drawObj
	else:
		trans = drawObj.gotoChild('transposable', False)
	if drawObj.localName == 'group':
		group = drawObj
	else:
		group = drawObj.gotoChild("group", False)
	if group is not None:
		groupDrawObjs = childElements(group, 'drawObj')
		for groupDrawObj in groupDrawObjs:
			moveChordSym(groupDrawObj, xOffs, yOffs)
	elif trans is not None:
		transDrawObjs = childElements(trans, 'drawObj')
		for transDrawObj in transDrawObjs:
			moveChordSym(transDrawObj, xOffs, yOffs)
	else:
		# Remaining regular text
		if drawObj.localName == 'text':
			text = drawObj
		else:
			text = drawObj.gotoChild('text', False)
		if text is not None:
			x = 0.0
			y = 0.0
			x_t = text.getAttribute('x')
			y_t = text.getAttribute('y')
			try:
				x = float(x_t)
				y = float(y_t)
			except:
				pass
			text.setAttribute('x', str(x+xOffs))
			text.setAttribute('y', str(y+yOffs))


def getGlobalFretIndex(noteObj):
	textObjs = getObjTextObjsByCriteria(noteObj, 'globalFretIndex')
	gfis = []
	for textObj in textObjs:
		content = getTextContent(textObj)
		try:
			dummy = int(content)
			gfis.append(textObj)
		except:
			pass
	if len(gfis) == 1:
		content = getTextContent(gfis[0])
		return content
	return ''

def getGenderHint(noteObj, keyPosRanges, excludePosRanges, globalFretIndexPosRanges, globalPitchIndexPosRanges, pos):
	simpleTexts = getSimpleTexts(noteObj)
	for text in simpleTexts:
		if text.lower() in ['major', 'maj', 'dur']:
			print("getGenderHint - got MAJOR mark!")
			return 'major'
		elif text.lower() in ['minor', 'min', 'moll']:
			print("getGenderHint - got MINOR mark!")
			return 'minor'

	chord = getChordOrStep(noteObj)
	if len(chord) > 0:
		if chord[0] in ['i', 'I', 'v', 'V']:
			schord = chord.split('[')
			if len(schord) == 2 and schord[1].count(']') == 1:
				pitches = getPitches(noteObj)
				if len(pitches) == 1:
					step = re.sub('(.*?)\[.*?\](.*?)','\\1\\2',chord)
					print("getGenderHint - schord, chord, step", schord, chord, step)
					hintTone = schord[1].split(']')[0]
					hintToneNum = '0'
					hintToneTune = '0'
					if hintTone.endswith('#'):
						hintToneNum = re.sub('([0-9]).*', '\\1', hintTone)
						hintToneTune = '1'
					elif hintTone.endswith('b'):
						hintToneNum = re.sub('([0-9]).*', '\\1', hintTone)
						hintToneTune = '-1'
					elif hintTone.find('+') > 0:
						hintToneNum = hintTone.split('+')[0]
						hintToneTune = hintTone.split('+')[1]
					elif hintTone.find('-') > 0:
						hintToneNum = hintTone.split('-')[0]
						hintToneTune = '-'+hintTone.split('-')[1]
					else:
						hintToneNum = hintTone
						hintToneTune = 0

					print('getGenderHint - hint tone detected: (tone, tone index, tune)', hintTone, hintToneNum, hintToneTune)
					try:
						hintToneNum = int(hintToneNum)
					except:
						hintToneNum = 0
					try:
						hintToneTune = int(hintToneTune)
					except:
						hintToneTune = 0

					pitchIndex = getItemAtPos(globalPitchIndexPosRanges, pos)
					try:
						if type(pitchIndex) is list:
							pitchIndex = pitchIndex[0]
						pitchIndex = int(pitchIndex)
					except:
						pitchIndex = 0
						
					print('getGenderHint - pitch index:', pitchIndex)

					pitch = pitches[0][:-1]
					tkey = getItemAtPos(keyPosRanges, pos)
					print('getGenderHint - tkey:', tkey)
					if type(tkey) is list:
						tkey = tkey[0]
					if tkey == '':
						tkey = '0'
					key = acaChords.get_keys_from_circle(int(tkey))
					root_major = key[0]
					root_minor = re.sub("m$", "", key[1])
					print('getGenderHint - root_major before:', root_major)
					print('getGenderHint - root_minor before:', root_minor)
					root_major = acaChords.get_transposed_chord_name(root_major, pitchIndex, acaChords.has_sharps_circle(acaChords.get_base_tone(root_major), False, True))
					root_minor = acaChords.get_transposed_chord_name(root_minor, pitchIndex, acaChords.has_sharps_circle(acaChords.get_base_tone(root_minor), True, True))
					print('getGenderHint - root_major after:', root_major)
					print('getGenderHint - root_minor after:', root_minor)
					major_scale = acaChords.get_major_key_scale(root_major)
					minor_scale = acaChords.get_minor_key_scale(root_minor)
					major_scale = acaChords.force_sign(major_scale, '#')
					minor_scale = acaChords.force_sign(minor_scale, '#')
					print('getGenderHint - hintToneNum:', hintToneNum)
					print('getGenderHint - hintToneTune:', hintToneTune)
					print('getGenderHint - key:', key)
					print('getGenderHint - '+root_major+'-scale :', major_scale)
					print('getGenderHint - '+root_minor+'m-scale:', minor_scale)
					print('getGenderHint - chord (step):', step)
					chord_major = acaChords.step_to_chord(step, root_major, seven_flat = setStep7Flat)
					chord_minor = acaChords.step_to_chord(step, root_minor, seven_flat = setStep7Flat)
					print('getGenderHint - chord (major-based):', chord_major)
					print('getGenderHint - chord (minor-based):', chord_minor)
					step_major = acaChords.get_chord_prefix(chord_major)
					step_minor = acaChords.get_chord_prefix(chord_minor)

					if acaChords.check_chord(step, setGermanH):
						if acaChords.is_minor(step):
							step_major_scale = acaChords.get_minor_key_scale(step_major)
							step_minor_scale = acaChords.get_minor_key_scale(step_minor)
						else:
							step_major_scale = acaChords.get_major_key_scale(step_major)
							step_minor_scale = acaChords.get_major_key_scale(step_minor)
						step_major_scale = acaChords.force_sign(step_major_scale, '#')
						step_minor_scale = acaChords.force_sign(step_minor_scale, '#')
						print('getGenderHint - '+chord_major+'-scale:', step_major_scale)
						print('getGenderHint - '+chord_minor+'-scale:', step_minor_scale)

						major_tones_o = acaChords.chord_to_tones(chord_major,2)
						minor_tones_o = acaChords.chord_to_tones(chord_minor,2)
						major_tones = []
						minor_tones = []
						for tone in major_tones_o:
							major_tones.append(re.sub('[0-9]','',tone))
						for tone in minor_tones_o:
							minor_tones.append(re.sub('[0-9]','',tone))
						major_tones = acaChords.force_sign(major_tones, '#')
						minor_tones = acaChords.force_sign(minor_tones, '#')
						print('getGenderHint - tones (major-based):', major_tones)
						print('getGenderHint - tones (minor-based):', minor_tones)
						fretIndex = 0
						globalFretIndex = getItemAtPos(globalFretIndexPosRanges, pos)
						try:
							if type(globalFretIndex) is list:
								globalFretIndex = globalFretIndex[0]
							globalFretIndex = int(globalFretIndex)
						except:
							globalFretIndex = None
						localFretIndex = None
						localFretIndexTextObjs = getObjTextObjsByCriteria(noteObj, 'localFretIndex')
						if len(localFretIndexTextObjs) == 1 and globalFretIndex is not None:
							operation = 'rep'
							content = getTextContent(localFretIndexTextObjs[0]).strip()
							if len(content) > 1:
								if content[0] == '+':
									operation = 'add'
									content = content[1:]
								elif content[0] == '-':
									operation = 'sub'
									content = content[1:]
							try:
								localFretIndex = int(content)
							except:
								pass
						if localFretIndex is not None and globalFretIndex is not None:
							if operation == 'add':
								fretIndex = globalFretIndex + localFretIndex
							elif operation == 'sub':
								fretIndex = globalFretIndex - localFretIndex
							else:
								fretIndex = localFretIndex
						else:
							if globalFretIndex is not None:
								fretIndex = globalFretIndex
						print('getGenderHint - fret index', fretIndex)
						pitch = re.sub('[0-9]', '', acaChords.pitchFromMidi(acaChords.midiPitch(pitch+"2")-hintToneTune+fretIndex))
						print('getGenderHint - pitch:', pitch)
						index_of_pitch_maj = -1
						index_of_pitch_min = -1
						if step_major_scale.count(pitch) > 0:
							index_of_pitch_maj = step_major_scale.index(pitch) + 1
						if step_minor_scale.count(pitch) > 0:
							index_of_pitch_min = step_minor_scale.index(pitch) + 1
						print('getGenderHint - index of pitch in '+chord_major+' scale:', index_of_pitch_maj)
						print('getGenderHint - index of pitch in '+chord_minor+' scale:', index_of_pitch_min)
						if hintToneNum == index_of_pitch_maj:
							print("getGenderHint - Matched tone on MAJOR scale!")
							return 'major'
						elif hintToneNum == index_of_pitch_min:
							print("getGenderHint - Matched tone on MINOR scale!")
							return 'minor'
						else:
							print("getGenderHint - Tone does not match any scale!")
	return ''

def getChordOrStep(noteObj):
	duration = getObjDuration(noteObj)
	if duration is not None:
		simpleTexts = getSimpleTexts(noteObj)
		text = getChordSym(noteObj)
		if len(text) > 0 and simpleTexts.count('*') == 0:
			chordJoined = str.join('',text)
			chordJoined = acaChords.chord_angsax(chordJoined, setGermanH)
			try:
				inum = int(chordJoined)
				chordJoined = ''
			except:
				chordNoAlt = re.sub('(.*)<altslash>.+', '\\1', chordJoined)
				valid = acaChords.check_chord(chordNoAlt, setGermanH)
				if valid:
					return chordJoined
				else:
					return ''
	return ''

def getKey(noteObj):
	if noteObj.localName == 'keySign':
		fifths = noteObj.getAttribute('fifths')
		if fifths is not None:
			return int(fifths)
	return None

def getKeys(noteObj):
	key = getKey(noteObj)
	if key is not None:
		return str(key)
	return ''

def getChordPositions(staff, countOnly = False):
	return getItemPositions(getChordOrStep, staff, countOnly)

def getGlobalFretIndexPositions(staff, countOnly = False):
	return getItemPositions(getGlobalFretIndex, staff, countOnly)

def getPitchIndexPositions(staff, countOnly = False):
	return getItemPositions(getPitchIndex, staff, countOnly)

def getKeyPositions(staff, countOnly = False):
	return getItemPositions(getKeys, staff, countOnly)

def getGenderHintPositions(staff, keyPosRanges, excludePosRanges, globalFretIndexPosRanges, globalPitchIndexPosRanges, countOnly = False):
	return getItemPositions(getGenderHint, staff, countOnly, keyPosRanges, excludePosRanges, globalFretIndexPosRanges, globalPitchIndexPosRanges)

def getItemPositions(objFunc, staff, countOnly = False, posRanges = None, posRanges2 = None, posRanges3 = None, posRanges4 = None):
	ovoices, voices = getChildren(staff, 'voice')
	itemPosList = []
	for voice in voices:
		pos = -0.001
		onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
		for obj in noteObjects:
			duration = getObjDuration(obj)
			if posRanges4 is not None:
				text = objFunc(obj, posRanges, posRanges2, posRanges3, posRanges4, pos)
			elif posRanges3 is not None:
				text = objFunc(obj, posRanges, posRanges2, posRanges3, pos)
			elif posRanges2 is not None:
				text = objFunc(obj, posRanges, posRanges2, pos)
			elif posRanges is not None:
				text = objFunc(obj, posRanges, pos)
			else:
				text = objFunc(obj)
			if len(text) > 0:
				itemPosList.append((pos, text))
			voltas = obj.getElementsByTagName('volta')
			if voltas.length > 0 and not countOnly:
				itemPosList.append((pos-0.001, 'volta'))

			if duration is not None:
				pos = pos + duration
	itemPosList.sort()
	print("itemPosList", itemPosList)
	return itemPosList

def genItemPositionRanges(itemPosList, totalTime, initItem = [], countOnly = False):
	lastPos = None
	pos = -0.001
	retval = []
	prevItem = initItem
	item = ''
	if len(prevItem) > 0:
		item = prevItem[0]

	for itemPos in itemPosList:
		text = itemPos[1]
		pos = itemPos[0]
		if text == 'volta':
			if len(prevItem) < 2:
				prevItem.insert(0, item)
			else:
				oldItem = item
				item = prevItem[-1]
				prevItem = prevItem[1:]
				prevItem[0] = oldItem
			text = item

		try:
			print('text', text)
			item = text

			if len(prevItem) > 0:
				retval.append(((lastPos, pos), prevItem[:]))
			if len(prevItem) == 0:
				prevItem.append(item)
			else:
				prevItem[0] = item

			lastPos = pos
		except:
			print('CRASH!!!')
			pass

	if len(prevItem) > 0 and lastPos is not None:
		retval.append(((lastPos, totalTime), prevItem[:]))
	else:
		if not countOnly:
			retval.append(((0.0, totalTime), prevItem[:]))
	return retval

# Get Item at specific position
def getItemAtPos(itemRanges, pos):
	for itemRange in itemRanges:
		if pos >= itemRange[0][0] and pos < itemRange[0][1]:
			return itemRange[1]
	return ''

def hasItemAtPos(itemRanges, pos):
	for itemRange in itemRanges:
		if itemRange[0][0] is not None:
			if pos >= itemRange[0][0]-0.001 and pos < itemRange[0][0]+0.002:
				return itemRange[1]
	return None

def getMarkedChordPitches(score):
	sys1 = curSelection()[0][0]
	sys2 = curSelection()[1][0]
	sta1 = curSelection()[0][1]
	sta2 = curSelection()[1][1]
	voi1 = curSelection()[0][2]
	voi2 = curSelection()[1][2]
	nObj1 = curSelection()[0][3]
	nObj2 = curSelection()[1][3]

	osystens, systems = getChildren(score, 'system')
	systemID = 0
        for system in systems:
                ostaves, staves = getChildren(system, 'staff')
		staffID = 0
        	for staff in staves:
                	ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
        		for voice in voices:
				oobjs, objs = getChildren(voice, 'noteObjects')
				numObjs = len(objs)
				objID = 0
				for i in range(0, numObjs):
					obj = objs[i]
					dura = getObjDuration(obj)
					if dura is not None:
						if (systemID, staffID, voiceID) == (sys1, sta1, voi1) and (systemID, staffID, voiceID) == (sys2, sta2, voi2):
							if nObj1+1 == nObj2 or nObj2+1 == nObj1:
								if nObj1 == objID:
									return getPitches(obj)
								elif nObj2 == objID:
									return getPitches(obj)
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	return None
	
def getPosAtCursor(score):
	pos = 0.0
	sys1 = curSelection()[0][0]
	sta1 = curSelection()[0][1]
	voi1 = curSelection()[0][2]
	nObj1 = curSelection()[0][3]

	osystens, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		savedPosSystem = pos
		for staff in staves:
			pos = savedPosSystem
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			savedPosStaff = pos
			for voice in voices:
				pos = savedPosStaff
				oobjs, objs = getChildren(voice, 'noteObjects')
				numObjs = len(objs)
				objID = 0
				for i in range(0, numObjs):
					obj = objs[i]
					if (systemID, staffID, voiceID, objID) == (sys1, sta1, voi1, nObj1):
						return pos
					dura = getObjDuration(obj)
					if dura is not None:
						pos = pos + dura
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	return None
		
def concatStaves(staves):
	print('concatStaves:', staves)
	newStaff = staves[0].cloneNode(deep = True)
	ovoices, voices = getChildren(newStaff, 'voice')
	print('concatStaves - numStaves:', len(staves))
	print('concatStaves - numVoices:', len(voices))
	voiceNo = 0
	for voice in voices:
		voiceNoteObjs = voice.gotoChild('noteObjs')
		for staff in staves[1:]:
			osvoices, svoices = getChildren(staff, 'voice')
			osVoiceNo = 0
			for svoice in svoices:
				if osVoiceNo == voiceNo:
					onoteObjects, noteObjects = getChildren(svoice, 'noteObjects')
					noteObjID = 0
					for noteObj in noteObjects:
						newNoteObj = noteObj.cloneNode(deep=True)
						# Add key sign 0 (C/Am) if nothing is given
						if noteObjID == 1:
							if noteObjects[0].localName == 'clefSign' and noteObj.localName != 'keySign':
								newKeySign = doc.createElement('keySign') 
								newKeySign.setAttribute('fifths', '0')
								addNote(voice, newKeySign)
								print('concatStaves - add (brand new) keySign:', newKeySign)
						addNote(voice, newNoteObj)
						print('concatStaves - add note:', newNoteObj)
						noteObjID = noteObjID + 1
				osVoiceNo = osVoiceNo + 1
		voiceNo = voiceNo + 1
	return newStaff	

def addNote(voice, newNoteObj):
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newObjs = []
	for noteObj in noteObjs:
		newObjs.append(noteObj)
	newObjs.append(newNoteObj)
	setChildren(oNoteObjs, newObjs, 'noteObjects')

def getBassToneFromStep(step, bass, root):
	root = acaChords.force_sign(root, True)
	print('getToneNumFromChord - step:', step)
	print('getToneNumFromChord - root:', root)
	print('getToneNumFromChord - bass:', bass)
	chord = acaChords.step_to_chord(step, root, True, setStep7Flat)
	print('getToneNumFromChord - chord:', chord)
	if acaChords.is_minor(chord):
		scale = acaChords.get_minor_key_scale(root)
	else:
		scale = acaChords.get_major_key_scale(root)
	print('getToneNumFromChord - scale:', scale)
	try:
		index = int(bass) - 1
		tone = scale[index]
		print('getToneNumFromChord - tone:', tone)
		return tone
	except:
		pass
	return ''



def getToneNumFromChord(chord, bass):
	root = acaChords.get_chord_prefix(chord)
	root = acaChords.force_sign(root, True)
	bass = acaChords.force_sign(bass, True)
	print('getToneNumFromChord - chord:', chord)
	print('getToneNumFromChord - root:', root)
	print('getToneNumFromChord - bass:', bass)
	if acaChords.is_minor(chord):
		scale = acaChords.get_minor_key_scale(root)
	else:
		scale = acaChords.get_major_key_scale(root)
	print('getToneNumFromChord - scale:', scale)
	chord_tones_t = acaChords.chord_to_tones(root, 2, 0)
	chord_tones = []
	for ct in chord_tones_t:
		chord_tones.append(re.sub('[0-9]', '', ct))
	print('getToneNumFromChord - chord tones:', chord_tones)
	if scale.count(bass) > 0:
		index =  scale.index(bass)+1
		print('getToneNumFromChord - index:', index)
		return index
	else:
		return 0


def convertChord(chord, key, pitchIndex, bass):
	# setConvMode
	# 0: classic german (C, d, ...)
	# 1: classic angelsax (C, Dm, ...)
	# 2: generic-rom german (I, ii, ...)
	# 3: generic-rom angelsax (I, IIm, ...)
	# 4: generic-dec angelsax (1, 2m, ...)

	root = acaChords.get_chord_prefix(chord)
	sharp = True
	if len(root) > 1 and root.endswith('b'):
		sharp = False
	convMode = int(setConvMode)
	if convMode in [0, 1]:
		classic_chord = acaChords.step_to_chord(acaChords.chord_angsax(chord, setGermanH), key, prefer_sharp=sharp, seven_flat=setStep7Flat)
		if classic_chord != '':
			classic_chord = acaChords.get_transposed_chord_name(classic_chord, pitchIndex, sharp)
		if not setNit:
			classic_chord = re.sub('(.*)\\[.+\\](.*)', '\\1\\2', classic_chord)
		targ_chord = classic_chord
		if bass != '' and classic_chord != '':
			tone = getBassToneFromStep(acaChords.chord_angsax(chord, setGermanH), bass, acaChords.get_chord_prefix(acaChords.chord_angsax(classic_chord, setGermanH)))
			if tone != '':
				bass = tone
			else:
				targ_chord = ''
		if targ_chord == '':
			targ_chord = acaChords.chord_angsax(chord, setGermanH)
	else:
		trans_chord = chord
		if trans_chord != '':
			trans_chord = acaChords.chord_angsax(chord, setGermanH)
			trans_chord = acaChords.get_transposed_chord_name(trans_chord, -pitchIndex, sharp)
		generic_chord = acaChords.chord_to_step(acaChords.chord_angsax(trans_chord, setGermanH), key, prefer_sharp=sharp, seven_flat=setStep7Flat)
		targ_chord = generic_chord
		if bass != '':
			tn = getToneNumFromChord(acaChords.chord_angsax(chord, setGermanH), bass)
			if tn > 0:
				bass = str(tn)
			else:
				targ_chord = ''
		if targ_chord == '':
			targ_chord = acaChords.chord_angsax(chord, setGermanH)

	root = acaChords.get_chord_prefix(targ_chord)
	if root == '':
		return chord, '', ''
	root_rest = ''
	if root.endswith('b') or root.endswith('#'):
		root_rest = root[-1]
		root = root[:-1]
	deri = acaChords.get_chord_suffix(targ_chord)
	roman_steps = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
	decimal_steps = ['1', '2', '3', '4', '5', '6', '7']
	if convMode == 4:
		index = 0
		if roman_steps.count(root) > 0:
			index = roman_steps.index(root)
		elif decimal_steps.count(root) > 0:
			index = decimal_steps.index(root)
		root = str(index+1)
	root = root + root_rest		
	if acaChords.is_minor(targ_chord):
		if convMode in [1, 3, 4]:
			root = root + 'm'
			deri = deri[1:]
		else:
			root = root.lower()
			deri = deri[1:]
	return root, deri, bass

def readChordFile(score):
	osystens, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		for staff in staves:
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				for obj in objs:
					if obj.localName == 'chord':
						chds = getChordSyms(obj)
						simple_texts = getSimpleTexts(obj)
						if len(chds) > 0:
							cts = []
							for chd in chds:
								cts.append(acaChords.get_chord_suffix(chd))
							print('readChordFile: cts:', cts)
							oheads, heads = getChildren(obj, 'head')
							m_pitches = []
							for head in heads:
								pitch = cap2acaTone(head,False)
								m_pitch = acaChords.midiPitch(pitch)
								m_pitches.append(m_pitch)
							m_pitches.sort()
							new_coeff = [0]
							old_m_pitch = None
							for m_pitch in m_pitches:
								if old_m_pitch is not None:
									diff = m_pitch - old_m_pitch
									new_coeff.append(diff)
								old_m_pitch = m_pitch
							print('readChordFile: coeff:', new_coeff)
							ct = acaChords.get_chord_type_by_coefficients(new_coeff) 
							print('readChordFile: chordType:', ct)
							is_embedded = ct is not None
							print('readChordFile: already in acaChords:',is_embedded)
							if ct is not None:
								new_token_list = ct.tokenList
								for token in cts:
									if token not in new_token_list:
										print('readChordFile: adding token',token,'for',chd)
										new_token_list.append(token)
								new_ct = ChordType(new_token_list, ct.major, ct.minor, ct.group, ct.coeff, ct.allow_inversions)
								acaChords.chord_types.remove(ct)
								acaChords.chord_types.append(new_ct)
								acaChords.rebuild_chord_maps()
							else:
								major = False
								minor = False
								group = ''
								if new_coeff[0:3] == [0, 4, 3]:
									major = True
								elif new_coeff[0:3] == [0, 3, 4]:
									minor = True
								elif new_coeff[0:3] == [0, 3, 3]:
									group = "dim"
								elif new_coeff[0:3] == [0, 4, 4]:
									group = "aug"
								elif new_coeff[0:3] == [0, 2, 5]:
									group = "sus2"
								elif new_coeff[0:3] == [0, 5, 2]:
									group = "sus4"
								allow_inv = False
								for text in simple_texts:
									if text.lower().strip() == 'yes':
										allow_inv = True
								new_ct = ChordType(cts, major, minor, group, new_coeff, allow_inv)
								for token in cts:
									ct = acaChords.get_chord_type_by_token(token)
									if ct is not None:
										print('readChordFile: replace',ct.tokenList[0],'by', new_ct)
										acaChords.chord_types.remove(ct)
										acaChords.chord_types.append(new_ct)
										acaChords.rebuild_chord_maps()
									else:
										print('readChordFile: adding new chord', new_ct)
										acaChords.chord_types.append(new_ct)
										acaChords.rebuild_chord_maps()
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	print('readChordFile: chord_types', acaChords.chord_types)
	print('readChordFile: Score has '+str(systemID)+' systems.')

def readNotePatterns(score):
	osystens, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		for staff in staves:
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				for obj in objs:
					if obj.localName == 'chord':
						oheads, heads = getChildren(obj, 'head')
						for head in heads:
							pitch = head.getAttribute('pitch')
							print('pitch:', pitch)
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	print('Score has '+str(systemID)+' systems.')

def csConvert(score, noteRange = None):
	if chordsStaffName == '-':
		msgNoChords()
		return
    
	chordStaves = getStavesByLayout(score, chordsStaffName)
	wholeChordStaff = concatStaves(chordStaves)
	wTotalTime = getStaffTotalTime(wholeChordStaff)

	wKeyPositions = getKeyPositions(wholeChordStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)

	wGlobalFretIndexPositions = getGlobalFretIndexPositions(wholeChordStaff)
	wGlobalFretIndexPosRanges = genItemPositionRanges(wGlobalFretIndexPositions, wTotalTime, ['0'])
	print('whole global fret indexes', wGlobalFretIndexPosRanges)

	wPitchIndexPositions = getPitchIndexPositions(wholeChordStaff)
	wPitchIndexPosRanges = genItemPositionRanges(wPitchIndexPositions, wTotalTime, [''])
	print('whole pitch indexes', wPitchIndexPosRanges)

	wGenderHintPositions = getGenderHintPositions(wholeChordStaff, wKeyPosRanges, [], wGlobalFretIndexPosRanges, wPitchIndexPosRanges)
	wGenderHintPosRanges = genItemPositionRanges(wGenderHintPositions, wTotalTime, [])
	print('whole gender hint', wGenderHintPosRanges)

	
	osystens, systems = getChildren(score, 'system')
	pos = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		savedPos = pos
		for staff in staves:
			pos = savedPos
			if staff.getAttribute('layout') == chordsStaffName:
				ovoices, voices = getChildren(staff, 'voice')
				savedPos2 = pos
				for voice in voices:
					pos = savedPos2
					oObjs, objs = getChildren(voice, 'noteObjects')
					for i in range(0,len(objs)):
						obj = objs[i]
						duration = getObjDuration(obj)

						if duration is not None:
							chord = getChordSym(obj)
							if type(chord) is list:
								chord = str.join('', chord)
							chord_sp_as = chord.split('<altslash>')
							bass = ''
							if len(chord_sp_as) > 0:
								chord = chord_sp_as[0]
							if len(chord_sp_as) > 1:
								bass = chord_sp_as[1]

							elif chord != '' and acaChords.check_chord(chord):
								x, y, above, auto = getChordSymProps(obj)
								removeChordSym(obj)
								tkey = getItemAtPos(wKeyPosRanges, pos)
								if type(tkey) is list:
									tkey = tkey[0]
								try:
									tkey = int(tkey)
								except:
									tkey = 0
								keys = acaChords.get_keys_from_circle(tkey)
								ghint = getItemAtPos(wGenderHintPosRanges, pos)
								if type(ghint) is list and len(ghint) > 0:
									ghint = ghint[0]

								if type(chord) is list:
									chord = chord[0]
								if ghint == 'minor' and setAutoGender:
									key = keys[1]
								elif ghint == 'major' and setAutoGender:
									key = keys[0]
								else:
									if setMajor:
										key = keys[0]
									else:
										key = keys[1]
								root_extra = ''
								chord_split = chord.split('[')
								chord = chord_split[0]
								if len(chord_split) > 1:
									root_extra = '['+chord_split[1]
								pitchIndex = getItemAtPos(wPitchIndexPosRanges, pos)
								if type(pitchIndex) is list and len(pitchIndex) > 0:
									pitchIndex = pitchIndex[0]
								try:
									pitchIndex = int(pitchIndex)
								except:
									pitchIndex = 0
								root,deri,bass = convertChord(chord, key, pitchIndex, bass)
								if len(root) > 0 and root[0].upper() in ['A','B','C','D','E','F','G','H']:
									if setNit:
										pass
									else:
										root_extra = ''
									setTransposableChordSym(obj, x, y, above, auto, root+root_extra, deri, bass)
								else:
									if hasItemAtPos(wGenderHintPosRanges, pos) not in [None, []]:
										pass
									else:
										root_extra = ''
									setSimpleChordSym(obj, x, y, above, auto, root+root_extra, deri, bass)
							pos = pos + duration

def msgNoChords():
	messageBox('Keine Chords/Steps in Cursorzeile', 'Es wurden keine Chords/Steps in der Cursorzeile gefunden.  Diese bitte mit dem Plugin "Transponierbares Akkordsymbol" setzen.')

def msgPitchMissingColor():
	messageBox('Achtung', 'Der Pitch-Index muss farblich gekennzeichnet sein (Standard: Rot).')

setPitchIndex = True

class Config:
	def __init__(self):
		self.file = ScriptOptions()
		self.dic = {}

	def get(self, varName, defValue = None):
		try:
			value = self.dic.get(varName)
			if defValue is not None:
				if value is None:
					value = defValue
					if type(defValue) is bool:
						defValue = str(value).lower
					elif type(defValue) is int:
						defValue = int(value)
					self.set(varName, defValue)
				else:
					if type(defValue) is bool:
						if value == 'true':
							value = True
						elif value == 'false':
							value = False
						elif value == '1':
							value = True
						elif value == '0':
							value = False
					if type(defValue) is int:
						value = int(value)
			return value
		except:
			pass
		return None
	
	def set(self, varName, value):
		if type(value) is bool:
			value = str(value).lower()
		if type(value) is int:
			value = str(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)
				if value == 'false':
					value = False
				elif value == 'true':
					value = True
				self.dic.update({key: value})
			print("loaded", self.dic)
		except:
			print("Could not open configuration file",self.file)

	def save(self):
		try:
			for key in self.dic.keys():
				value = self.dic.get(key)
				if type(value) is bool:
					value = str(value).lower()
				self.dic.update({key: value})
			self.file.set(self.dic)
		except:
			print("Could not save configuration file",self.file)

# Get color and font size for a given keyword
def getColorAndSizeForKeyword(score, keyword, case_sens=False):
	parsed = False
	if case_sens == False:
		keyword = keyword.lower()
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:
			staffName = staff.getAttribute('layout')
			ovoices, voices = getChildren(staff, 'voice')
			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 = text.getElementsByTagName('content')
							data = ''
							if content is not None and len(content) > 0:
								fs = content[0].firstChild
								if fs is not None:
									data = fs.data
	
							fonts = text.getElementsByTagName('font')
							if case_sens == False:
								data = data.lower()
							if fonts.length > 0 and data.replace("!","").startswith(keyword):
								if data == (keyword+'!'):
									parsed = True
								color = fonts[0].getAttribute('color')
								height = int(fonts[0].getAttribute('height'))
								return (color, height, staffName, parsed)
	return (u'', 0, u'<?>', False)

def getPitchIndex(noteObj):
	texts = noteObj.getElementsByTagName('text')
	if texts.length > 0:
		for text in texts:
			try:
				content = text.getElementsByTagName('content')[0].firstChild.data
				fonts = text.getElementsByTagName('font')
				if fonts.length > 0 and setPitchIndex:
					color = fonts[0].getAttribute('color')
					height = int(fonts[0].getAttribute('height'))
					if height == pitchIndexSize and color == pitchIndexColor and content.strip().lower() != 'pitch':
						return content.lower().replace('pitch ', '')
			except:
				pass
	return u''

def getPitchIndexCount(score, staff_name):
	osystens, systems = getChildren(score, 'system')
	count = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		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]
					pitchIndex = getPitchIndex(obj)
					if len(pitchIndex) > 0:
						count = count + 1
	return count

config = Config()

isAln = False
def analyzeScore(score):
	global chordsStaffName
	global cursorKeys
	global setGermanH
	global setStep7Flat
	global pitchIndexColor
	global pitchIndexSize             
	global pitchIndexCount
	global estNotation
	global estStyle
	global estFretIndexFontSize	
	global estFretIndexFontColor
	global estFretIndexStaffName
	global isAln

	chordsStaffName = '-'
	cursorKeys = ['-', '-']
	estNotation = '-'
	estStyle = '-'
	chordsStaffIDs = []
	setGermanH = False
	setStep7Flat = True

	cursor = curSelection()[0]
	print('cursor', cursor)

	estFretIndexFontColor, estFretIndexFontSize, estFretIndexStaffName, isAln = getFretColorAndSize(score)

	pitchIndexColor, pitchIndexSize, pitchIndexStaffName, pitchIndexParsed = getColorAndSizeForKeyword(score, u'Pitch')
	print('pitchIndexStaffName', pitchIndexStaffName)
	pitchIndexCount = getPitchIndexCount(score, pitchIndexStaffName)
	if pitchIndexSize > 0 and (pitchIndexColor == '000000' or pitchIndexColor == ''):
		msgPitchMissingColor()

	chordNotations = []
	chordStyles = []

	osystens, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		for staff in staves:
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			hasChords = False
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				for obj in objs:
					chord = str.join('', getChordSym(obj))
					print('checking chord', chord)
					if (acaChords.check_chord(chord) or acaChords.check_chord(chord, True)) and staffID == cursor[1]:
						hasChords = True
						ct = getChordType(chord)
						print('chord '+chord+' is: ', ct)
						if ct[0] != '':
							chordNotations.append(ct[0])
						if ct[1] != '':
							chordStyles.append(ct[1])
					objID = objID + 1
				voiceID = voiceID + 1

			if hasChords:
				chordsStaffIDs.append(staffID)

			staffID = staffID + 1
		systemID = systemID + 1

	# Examine Chord Notation
	if len(chordNotations) > 0:
		if chordNotations[0] == 'classic':
			estNotation = 'klassisch'
		elif chordNotations[0] == 'generic-rom':
			estNotation = 'generisch (römisch)'
		elif chordNotations[0] == 'generic-dec':
			estNotation = 'generisch (dezimal)'
		diffCount = 0
		for chordNotation in chordNotations:
			if chordNotation != chordNotations[0]:
				diffCount = diffCount + 1
		if diffCount > 0:
			estNotation = estNotation + ' (' + str(diffCount) + ' Diff.)'

	print('styles:', chordStyles)
	# Examine Chord Style
	if len(chordStyles) > 0:
		if chordStyles[0] == 'german':
			estStyle = 'deutsch'
		elif chordStyles[0] == 'angSax':
			estStyle = 'angelsächsisch'
		diffCount = 0
		for chordStyle in chordStyles:
			if chordStyle != chordStyles[0]:
				diffCount = diffCount + 1
		if diffCount > 0:
			estStyle = estStyle + ' (' + str(diffCount) + ' Diff.)'

	# Examine Chord Staff
	if len(chordsStaffIDs) > 0:
		val = chordsStaffIDs[0]
		if chordsStaffIDs.count(val) == len(chordsStaffIDs):
			layout = score.gotoChild("layout")
			ostaffLayouts, staffLayouts = getChildren(layout, 'staffLayout', forcedPlural = 'staves')
			layout = staffLayouts[val]
			chordsStaffName = layout.getAttribute('description')

	if chordsStaffName == '-':
		msgNoChords()
		return
    
	chordStaves = getStavesByLayout(score, chordsStaffName)
	wholeChordStaff = concatStaves(chordStaves)
	wTotalTime = getStaffTotalTime(wholeChordStaff)

	wKeyPositions = getKeyPositions(wholeChordStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)

	wGlobalFretIndexPositions = getGlobalFretIndexPositions(wholeChordStaff)
	wGlobalFretIndexPosRanges = genItemPositionRanges(wGlobalFretIndexPositions, wTotalTime, ['0'])
	print('whole global fret indexes', wGlobalFretIndexPosRanges)

	wPitchIndexPositions = getPitchIndexPositions(wholeChordStaff)
	wPitchIndexPosRanges = genItemPositionRanges(wPitchIndexPositions, wTotalTime, [''])
	print('whole pitch indexes', wPitchIndexPosRanges)

	wGenderHintPositions = getGenderHintPositions(wholeChordStaff, wKeyPosRanges, [], wGlobalFretIndexPosRanges, wPitchIndexPosRanges)
	wGenderHintPosRanges = genItemPositionRanges(wGenderHintPositions, wTotalTime, [])
	print('whole gender hint', wGenderHintPosRanges)
			
	pos = getPosAtCursor(score)
	print('pos at cursor', pos)

	obj = cursorObj()
	mcTones = getMarkedChordPitches(score)
	print("mcTones", mcTones)
	if mcTones is not None:
		markedChord = acaChords.tones_to_chord(mcTones)
		if markedChord is None or markedChord == '':
			markedChord = '-'
		print("marked Chord", markedChord)

	if obj != 0:
		# Get Key at Cursor Position
		key = obj.curKey()
		gender = ''
		ckeys = acaChords.get_keys_from_circle(key)
		trans = 0
		if pos is not None:
			trans = getItemAtPos(wPitchIndexPosRanges, pos)
			try:
				if type(trans) is list:
					trans = trans[0]
				trans = int(trans)
			except:
				trans = 0
		ckeyMaj = acaChords.get_transposed_chord_name(ckeys[0], trans, True)
		ckeyMin = acaChords.get_transposed_chord_name(ckeys[1], trans, True)
		toneMaj = acaChords.get_tonic(ckeyMaj)
		toneMin = acaChords.get_tonic(ckeyMin)
		toneMaj = acaChords.aligned_tone_num(acaChords.midiPitch(toneMaj)+1)
		toneMin = acaChords.aligned_tone_num(acaChords.midiPitch(toneMin)+1)
		useSharpMaj = acaChords.has_sharps_circle(toneMaj,False)
		useSharpMin = acaChords.has_sharps_circle(toneMin,True)
		ckeyMaj = acaChords.get_transposed_chord_name(ckeys[0], trans, useSharpMaj)
		ckeyMin = acaChords.get_transposed_chord_name(ckeys[1], trans, useSharpMin)
		ckeys = [ckeyMaj, ckeyMin]
		if pos is not None:
			gender = getItemAtPos(wGenderHintPosRanges, pos)
			if type(gender) is list:
				if len(gender) >0:
					gender = gender[0]
				else:
					gender = ''
		print('gender at cursor, pos', gender, pos)
		if gender == 'major':
			ckeys = [ ckeys[0], '('+ckeys[1]+')' ]
		elif gender == 'minor':
			ckeys = [ '('+ckeys[0]+')', ckeys[1] ]
		cursorKeys = ckeys


# Analyze Phase
class ScoreAnalyze(ScoreChange):
	def changeScore(self, score):
		global doc
		doc = score.parentNode
		analyzeScore(score)

# Read Chord File
class ScoreReadChordFile(ScoreChange):
	def changeScore(self, score):
		readChordFile(score)
		global chordScore
		global docChords
		chordScore = score
		docChords = score.parentNode

# Plugin Processing Phase
class ScoreChange(ScoreChange):
	def changeScore(self, score):
		global doc
		doc = score.parentNode
		csConvert(score)

def beautify(text):
	return text.replace('#', '♯').replace('b', '♭')

# Plugin Dialog
def dialog():
	global pitchIndexCount

	global config
	global setConvMode
	global setGermanH
	global setStep7Flat
	global setMajor
	global setNit
	global setPitchIndex
	global setAutoGender

	global radioConvMode
	global checkGermanH
	global checkStep7Flat
	global checkMajor
	global checkNit
	global checkPitchIndex
	global checkAutoGender
	global checkCenterChordSyms
	global checkCenterIsyms

	global checkReset
	global isAln
	global estFretIndexFontColor
	global estFretIndexFontSize
	global estFretIndexStaffName

	config.load()
	setConvMode = config.get('setConvMode', 0)
	setGermanH = config.get('setGermanH', False)
	setStep7Flat = config.get('setStep7Flat', True)
	setMajor = config.get('setMajor', True)
	setNit = config.get('setNit', False)
	setPitchIndex = config.get('setPitchIndex', True)
	setAutoGender = config.get('setAutoGender', True)

	wdt = 52
	spacer = Label('')

	labelChordStaffName = Label('Akkord-/Stufen-Notenzeilenname', width = wdt / 2 + 2)
	labelNotation = Label('Klassische oder generische Darstellung', width = wdt / 2 + 2)
	labelStyle = Label('Stil (Moll-Akkorde)', width = wdt / 2 + 2)
	labelEstKey = Label('Tonart (an Cursorposition)', width = wdt / 2 + 2)
	fretIndexColor = Label('Fret-Index Font-Farbe', width=wdt / 2 + 2)
	fretIndexHeight = Label('Fret-Index Font-Größe', width=wdt / 2 + 2)
	fretIndexActive = Label('Fret-Index aktuell aktiv', width=wdt / 2 + 2)
	labelEstPitchIndexColor = Label('Pitch-Index Font-Farbe', width = wdt / 2 + 2)
	labelEstPitchIndexSize = Label('Pitch-Index Font-Größe', width = wdt / 2 + 2)
	labelEstPitchIndexNum = Label('Anzahl Pitch-Indexe', width = wdt / 2 + 2)
	vBoxEstLabels = VBox([labelChordStaffName, labelNotation, labelStyle, labelEstKey, fretIndexColor, fretIndexHeight, fretIndexActive, labelEstPitchIndexColor, labelEstPitchIndexSize, labelEstPitchIndexNum], width = wdt/2) 

	labelChordStaffNameValue = Label(': ' + chordsStaffName, width = wdt / 2 - 3)
	labelNotationValue = Label(': ' + estNotation, width = wdt / 2 - 3)
	labelStyleValue = Label(': ' + estStyle, width = wdt / 2 - 3)
	labelEstKeyValue = Label(': ' + beautify(cursorKeys[0])+' / '+beautify(cursorKeys[1]), width = wdt / 2 - 3)
	fColor = '-'
	fSize = '-'
	isConv = '-'
	if estFretIndexFontColor != '':
		fColor = '#'+estFretIndexFontColor
		fSize = str(estFretIndexFontSize)+' px'
		if isAln:
			isConv = "Ja"
		else:
			isConv = "Nein"
	fretIndexColorValue = Label(': '+fColor, width=wdt / 2 - 3)
	fretIndexHeightValue = Label(': '+fSize, fg = estFretIndexFontColor, width=wdt / 2 - 3)
	fretIndexActiveValue = Label(': '+isConv, width=wdt / 2 - 3)
	colorName = "#"+pitchIndexColor
	if pitchIndexColor == '':       
		colorName = '-' 
	labelEstPitchIndexColorValue = Label(': ' + colorName, width = wdt / 2 - 3)
	fontSize = str(pitchIndexSize)
	if pitchIndexSize == 0: 
		fontSize = '-'
	labelEstPitchIndexSizeValue = Label(': ' + fontSize, width = wdt / 2 - 3)
	if pitchIndexColor == '':
		colorName = '-'
		printedPitchIndexCount = '-'
	else:
		printedPitchIndexCount = str(pitchIndexCount)
	labelEstPitchIndexNumValue = Label(': ' + str(printedPitchIndexCount), width = wdt / 2 - 3)
	vBoxEstValues = VBox([labelChordStaffNameValue, labelNotationValue, labelStyleValue, labelEstKeyValue, fretIndexColorValue, fretIndexHeightValue, fretIndexActiveValue, labelEstPitchIndexColorValue, labelEstPitchIndexSizeValue, labelEstPitchIndexNumValue], width = wdt/2) 

	hBoxEstProperties = HBox([vBoxEstLabels, vBoxEstValues], text = 'Ermittelte Eigenschaften aus Notenzeile mit Akkord-/Stufensymbolen')


	convOptions = [ 'klassisch deutsch '+beautify('(C,  d,  e,  F,  G7,  a,  Bb)'), \
			'klassisch angelsächsisch '+beautify('(C,  Dm,  Em,  F,  G7,  Am,  Bb)'), \
			'generisch (röm.) deutsch (I,  ii,  iii,  IV,  V7,  vi,  VII)', \
			'generisch (röm.) angelsächsisch (I,  IIm,  IIIm,  IV,  V7,  VIm,  VII)             ', \
			'generisch (dez.) angelsächsisch (1,  2m,  3m,  4,  5^7,  6m,  7)' ]
	radioConvMode = Radio(convOptions, text='Konvertierung der klassischen Akkord- oder generischen Stufensymbole nach', value=setConvMode, width = wdt)
	checkStep7Flat = CheckBox('Dur-Stufen VII und vii = Tonleiterposition 7'+beautify('b')+' (oder 7)', value = setStep7Flat, width = wdt-4)
	checkGermanH = CheckBox('', value = setGermanH)
	labelGermanH = Label('H als B interpretieren (→ B = B'+beautify('b')+')', value = setGermanH, width = wdt-3)
	labelGermanH1 = Label('* für Konvertierung von “generisch” nach “klassisch”', width = wdt - 3)
	labelGermanH2 = Label('   ersetze B gegen H (und B'+beautify('b')+' gegen B)', width = wdt - 3)
	labelGermanH3 = Label('* für Konvertierung von “klassisch” nach “generisch”', width = wdt-3)
	labelGermanH4 = Label('   interpretiere B als B'+beautify('b'), width = wdt-3)
	ghVBox = VBox([labelGermanH, labelGermanH1, labelGermanH2, labelGermanH3, labelGermanH4])
	ghHBox = HBox([VBox([checkGermanH, spacer, spacer]), ghVBox], width = wdt)

	vBoxSettings = VBox([HBox([checkStep7Flat]), ghHBox], height = 1)
	hBoxSettings = HBox([vBoxSettings], text = 'Einstellungen', width = wdt)

	checkReset = CheckBox('EINSTELLUNGEN ZURÜCKSETZEN', value=False, width=wdt)
	labelNit = Label('nIT (nummerischer Initial-Ton) auf klassische Darstellung anwenden', value=setNit, width=wdt-3)
	labelGenderInfo = Label('Bei klass.  Akk.  mit Tonartwechseln ggf.  Marker "maj",  "min" verwenden', width=wdt-3)
	checkNit = CheckBox('', value=setNit)
	vBoxNit = VBox([labelNit, labelGenderInfo])
	hBoxCheckNit = HBox([checkNit, vBoxNit])
	checkAutoGender = CheckBox('Globalen Tonartencharakter automatisch ermitteln (nur bei Stufensym.!)', value=setAutoGender, width=wdt)
	checkMajor = CheckBox('Globaler Tonartencharakter (Gender) = Dur', value=setMajor, width=wdt)
	checkPitchIndex = CheckBox('Pitch-Index (intrinsische Tonartenkorrektur) berücksichtigen', value=setPitchIndex, width=wdt)
	pluginBoxV= VBox([checkReset, hBoxCheckNit, checkAutoGender, checkMajor, checkPitchIndex])
	pluginBox = HBox([pluginBoxV], text = 'Plugin-Anwendung')

	vBox = VBox([hBoxEstProperties, spacer, radioConvMode, spacer, hBoxSettings, spacer, pluginBox], width = wdt)
	dlg = Dialog('aC.Converter', vBox)
	return dlg


if activeScore():
	chordFileName = getPersonalDataDir()+"scripts/Plugins-Chord-Collection.capx"
	try:
		chordFile = open(chordFileName)
		tempOutput = tempfile.mktemp('.capx')
		ScoreReadChordFile(chordFile, tempOutput)
		os.remove(tempOutput)
	except:
		print('warning: Plugin chords collection file not found, using embedded chords, only.')

	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:
		setConvMode = radioConvMode.value()
		setGermanH = checkGermanH.value()
		setStep7Flat = checkStep7Flat.value()
		setMajor = checkMajor.value()
		setNit = checkNit.value()
		setPitchIndex = checkPitchIndex.value()
		setAutoGender  = checkAutoGender.value()

		config.set('setConvMode', setConvMode)
		config.set('setGermanH', setGermanH)
		config.set('setStep7Flat', setStep7Flat)
		config.set('setPitchIndex', setPitchIndex)
		config.set('setNit', setNit)
		config.set('setMajor', setMajor)
		config.set('setAutoGender', setAutoGender)

		print(config.dic)
		config.save()

		activeScore().registerUndo("aC.Converter")
		tempInput = tempfile.mktemp('.capx')
		tempOutput = tempfile.mktemp('.capx')
		activeScore().write(tempInput)
		ScoreChange(tempInput, tempOutput)
		activeScore().read(tempOutput)
		os.remove(tempInput)
		os.remove(tempOutput)

