# -*- coding: UTF-8 -*-
""" capellaScript -- (C) 2025 Ken Haiker, acaMusic (www.acamusic.de)
                         Implementation: Brian Schueler
>>> aC.Arpeggiator
	Rev 0.3.21 (24.07.2026) --- Plugin allgemein für Musiker
	||
	Der "zweistufige" aC.Arpeggiator erzeugt zu einer mit klassischen
	Akkorden oder generischen Stufen versehenen Melodie-Notenzeile in einer 
	zusätzlichen Notenzeile akkord-konforme Melodie-Pattern.
	||
	Die erzeugte, taktweise organisierte Pattern-Notenzeile kann eine Bass-Spur  
	genauso sein wie eine melodie-verzierende Flöten- oder Violin-Spur. Ebenso 
	kann eine Schlagzeugspur mit akkord-unabhängigen Pattern generiert werden.
    ||
	Benötigt wird außer dem Plugin eine Pattern-Datei (z.B. mit Bass- oder Drum-Patterns). 
	||
    -------------   www.acaMusic.de   -------------
    ||
    Auf der Homepage gibt es sowohl ein ausführliches Handbuch zum Plugin als auch Beispiel-Pattern-Dateien.
	||
	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:  Aug 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-arpeggiator.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)

# 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)


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

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

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

# Get duration for a block of note/chord/rest
def getBlockDuration(block):
	duration = 0
	for obj in block:
		dura = getObjDuration(obj)
		if dura is not None:
			duration = duration + dura
	return duration

# Get the decimal duration of a note/chord/rest
def getObjDuration(noteObj):
	duration = noteObj.gotoChild('duration', False)
	# Only NoteObjects that have a duration
	if duration is not None:
		noDuration = duration.getAttribute('noDuration')
		if noDuration == 'true':
			return 0.0

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

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

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

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

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

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

def 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 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
		

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 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 setUsePitchIndex:
					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 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

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

	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_group(self, chord):
		chd = self.get_chord_type(chord)
		if chd is not None:
			return chd.group
		return ""

	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 ''
		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 step_to_chord(self, step, key, prefer_sharp = True, seven_flat = True):
		try:
			prefix = self.get_chord_prefix(step)
			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)

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("!","").strip().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 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

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)
	newDrawObj = doc.createElement('drawObj')

	basic = newDrawObj.gotoChild('basic', True)
	basic.setAttribute('scope', 'brace')
	basic.setAttribute('placement', 'above')
	basic.setAttribute('placementHint', 'performanceInstruction')

	text = newDrawObj.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)
	drawObjs.appendChild(newDrawObj)

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, setOct4)
			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, baseOct4):
	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]
	if baseOct4:
		octave = str(int(octave) + 1)
	return (base+octave, step)

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 getTopStaves(score):
	retval = []
	osystems, systems = getChildren(score, 'system')
	for system in systems:
		staffID = 0
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:
			if staffID == 0:
				retval.append(staff)
			staffID = staffID + 1
	return retval

def getStaffDistances(staff):
	extraDistance = staff.gotoChild('extraDistance', False)
	top = 0
	bottom = 0
	if extraDistance is not None:
		try:
			top = int(extraDistance.getAttribute('top'))
		except:
			pass
		try:
			bottom = int(extraDistance.getAttribute('bottom'))
		except:
			pass
	return (top, bottom)

def setStaffDistances(staff, top = 0, bottom = 0):
	extraDistance = staff.gotoChild('extraDistance', False)
	if extraDistance is not None:
		staff.removeChild(extraDistance)

	if top != 0 or bottom != 0:
		extraDistance = staff.gotoChild('extraDistance', True)
		if top != 0:
			extraDistance.setAttribute('top', str(top))
		if bottom != 0:
			extraDistance.setAttribute('bottom', str(bottom))

def clearStaff(staff):
	ovoices, voices = getChildren(staff, 'voice')
	for voice in voices:
		clearVoice(voice)

def newVoice(staff, clear = True):
	ovoices, voices = getChildren(staff, 'voice')
	numVoices = len(voices)
	for i in range(0, numVoices):
		if i == 0:
			newVoice = voices[i].cloneNode(deep=True)
			if newVoice.hasAttribute("stemDir"):
				newVoice.removeAttribute("stemDir")
			if clear:
				clearVoice(newVoice)
	voices.extend([newVoice])
	setChildren(ovoices, voices, 'voice')

def getChordAtPos(pos, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges):
	chd = getItemAtPos(wChordPosRanges, pos)
	tpitidx = getItemAtPos(wPitchIndexPosRanges, pos)
	pitidx = 0
	try:
		pitidx = int(tpitidx[0])
	except:
		pass
	print('chd:', chd)
	print('pitch, tpitch:', pitidx, tpitidx)
	if type(chd) is list:
		chd = chd[0]
	chd = chd.split('[')[0]
	if len(chd) > 0:
		if chd[0].upper() in ['I', 'V']:
			key = getItemAtPos(wKeyPosRanges, pos)
			keys = acaChords.get_keys_from_circle(int(key[0]))
			if setAutoGender:
				gender = getItemAtPos(wGenderHintPosRanges, pos)
				if len(gender)>0:
					gender = gender[0]
				else:
					gender = ''
				if gender == 'major':
					stepOne = keys[0]
				elif gender == 'minor':
					stepOne = keys[1]
				else:
					if setMajor:
						stepOne = keys[0]
					else:
						stepOne = keys[1]
			elif setMajor:
				stepOne = keys[0]
			else:
				stepOne = keys[1]
			chd_chd = chd
			extra = ''
			if chd.find('<altslash>') > 0:
				chd_chd = chd.split('<')[0]
				chd_bass = chd.split('>')[1]
				extra = '<altslash>' + chd_bass
			chd = acaChords.step_to_chord(chd_chd, stepOne, seven_flat = setStep7Flat) + extra
	if acaChords.check_chord(chd, setGermanH):
		chd = acaChords.get_transposed_chord_name(chd, pitidx, setSharp)
	return chd

def transposePatternAtIndex(voiceMelody, sPos, lPos, duration, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges, key, transposeHarm = True):
	oNoteObjs, noteObjs = getChildren(voiceMelody, 'noteObjects')
	pos = 0.001
	for obj in noteObjs:
		if obj.localName in ['chord', 'rest']:
			dura = getObjDuration(obj)
			pitch = getPitches(obj)
			gPos = sPos + pos
			if pos > lPos and pos < lPos + duration:
				chd = getChordAtPos(gPos, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges)
				if len(chd) > 0:
					thirds_ht_offset = 0
					fifths_ht_offset = 0
					root = acaChords.get_chord_prefix(chd)
					is_minor = acaChords.is_minor(chd)
					chd_group = acaChords.get_group(chd)
					root_mp = acaChords.midiPitch(root+"4")
					key_root = acaChords.get_keys_from_circle(int(key))[0]
					c_mp = acaChords.midiPitch(key_root+"4")
					diff_mp = root_mp - c_mp
					if transposeHarm == False:
						diff_mp = 0
					if setChangeThirdsAndFifths:
						if is_minor:
							thirds_ht_offset = -1
						elif chd_group == "b5":
							fifths_ht_offset = -1
						elif chd_group == "#5":
							fifths_ht_offset = 1
						elif chd_group == "sus2":
							thirds_ht_offset = -2
						elif chd_group == "sus4":
							thirds_ht_offset = 1
						elif chd_group == "dim":
							thirds_ht_offset = -1
							fifths_ht_offset = -2
						elif chd_group == "aug":
							fifths_ht_offset = 1
					transposeObj(obj, diff_mp, thirds_ht_offset, fifths_ht_offset)	
					print('pitch, chd, duration, dura, sPos, lPos, pos, gPos:',pitch, duration, dura, chd, sPos, lPos, pos, gPos)
			pos = pos + dura	

def transposeObj(noteObj, offset, thirds_ht_offset=0, fifths_ht_offset=0):
	oHeads, heads = getChildren(noteObj, 'head')	
	for head in heads:
		step = 0
		pitch = head.getAttribute('pitch')
		# minor case: deminish 3rd tone
		if pitch[0] == 'E':
			offset = offset + thirds_ht_offset 
		elif pitch[0] == 'G':
			offset = offset + fifths_ht_offset 
		alter = head.gotoChild('alter', False)
		if alter is not None:
			step = int(alter.getAttribute('step'))
		tone = pitch
		if step == 1:
			tone = tone[0] + '#' + tone[1]
		elif step == -1:
			tone = tone[0] + 'b' + tone[1]
		tone_mp = acaChords.midiPitch(tone)
		if setOct4:
			tone_mp = tone_mp - 12	
			mid_c_octave = 4
		else:
			mid_c_octave = 5
		# octave and pitch range
		int_setOctave = mid_c_octave
		try:
			int_setOctave = int(setOctave)
		except:
			pass
		rel_octave = int_setOctave - mid_c_octave
		tone_mp = tone_mp + rel_octave * 12
		minTone_mp = acaChords.midiPitch(setMinTone)
		maxTone_mp = acaChords.midiPitch(setMaxTone)
		newTone_mp = tone_mp + offset
		while newTone_mp > maxTone_mp:
			newTone_mp = newTone_mp - 12
		while newTone_mp < minTone_mp:
			newTone_mp = newTone_mp + 12
		newTone = acaChords.pitchFromMidi(newTone_mp)
		newTone = acaChords.force_sign(newTone, setSharp)
		pitch, step = aca2capTone(newTone, setOct4)
		head.setAttribute('pitch', pitch)
		if step == 0:
			if alter is not None:
				head.removeChild(alter)
		else:
			if alter is None:
				alter = head.gotoChild('alter', True)
			alter.setAttribute('step', str(step))

def getBlockFromNoteIndexToBeatEnd(noteObjs, i, timeSign):
	block = []
	noteLen = len(noteObjs)
	dura = 0.0
	tsDuration = getDurationByBase(timeSign)
	for i in range(i, noteLen):
		obj = noteObjs[i]
		objDura = getObjDuration(obj)
		if objDura is not None:
			dura = dura + objDura
		block.append(obj)
		if dura >= tsDuration - 0.001 or obj.localName == 'barline':
			return block
	return block

def fillPatternsInStaff(staff, lastKey, lastPat, sPos, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges, timeSign):
	ovoices, voices = getChildren(staff, 'voice')
	layout = staff.getAttribute('layout')
	print('fillPatternsInStaff: staff name:', layout)
	voiceID = 0
	voice1 = None
	voiceMelody = None
	voiceDrums = None
	patKey = lastKey
	currentPat = lastPat
	currentPatText = ''
	failedPatternNames = ['/']
	for voice in voices:
		if voiceID == 0:
			voice1 = voice
		if voiceID == 1:
			voiceMelody = voice
		if voiceID == 2:
			voiceDrums = voice

		voiceID = voiceID + 1

	if voiceMelody is not None:
		oNoteObjs, noteObjs = getChildren(voice1, 'noteObjects')
		pos = 0.0
		numObjs = len(noteObjs)
		for i in range(0, numObjs):
			obj = noteObjs[i]
			if obj.localName == 'timeSign':
				timeSign = obj.getAttribute('time')
			elif obj.localName in ['rest']:
				texts = getSimpleTexts(obj)
				block = getBlockFromNoteIndexToBeatEnd(noteObjs, i, timeSign)
				dura = getBlockDuration(block)
				objDura = getObjDuration(obj)
				startPos = pos
				endPos = pos + dura
				if len(texts) > 0:
					if len(texts) == 1:
						text = texts[0]
					elif len(texts) == 2:
						prefix = ''
						suffix = ''
						for t in texts:
							t = t.strip()
							if len(t) > 0:
								if t[0] in ['*', '/', '!']:
									prefix = t
								else:
									suffix = t
						text = prefix + suffix
					print('pattern text: ' + text)
					if text == '/':
						pass
					else:
						textWoAsterisk = re.sub('\*(.*)', '\\1', text)
						voiceInt = 1
						try:
							voiceInt = int(setPatternVoice)
						except:
							pass
						patKey, currentPat = getPatternByName(patScore, textWoAsterisk, voiceInt)
						currentPatText = text
					if currentPat is not None and not text.startswith('*'):
						chd = getChordAtPos(sPos + pos, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges)
						if len(chd) > 0 or setIgnoreUnknownChords == False or setUseHarmonization == False:
							patternDura = getBlockDuration(currentPat)
							duration = endPos - startPos
							if abs(patternDura - duration) > 0.002:
								if failedPatternNames.count(currentPatText) == 0:
									msgPatternOutOfBeat(patternDura, duration, currentPatText)
								failedPatternNames.append(currentPatText)
							else:
								index = removeNotesAtRange(voiceMelody, startPos, endPos)
								if index is not None:
									insertPatternAtIndex(voiceMelody, index, duration, currentPat)
									if setPatternStaffCharacter == 0:
										transposePatternAtIndex(voiceMelody, sPos, pos, duration, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges, patKey, transposeHarm=setUseHarmonization)
				pos = pos + objDura	
	return timeSign, patKey, currentPat
	
	
def removeNotesAtRange(voice, startPos, endPos):
	print('removeNotesAtRange: startPos, endPos:', startPos, endPos)
	pos = 0.0
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newNoteObjs = []
	index = 0
	retval = None
	for obj in noteObjs:
		dura = getObjDuration(obj)
		if dura is None:
			dura = 0.0
		print('removeNotesAtRange: dura, startPos, pos, obj', dura, startPos, pos, obj.localName)
		if pos < startPos - 0.001 or pos > endPos - 0.001 or obj.localName not in ['chord', 'rest']:
			newNoteObjs.append(obj.cloneNode(deep = True))
		elif retval is None:
			print('removeNotesAtRange: removed',obj.localName,'at',pos)
			retval = index
		pos = pos + dura
		index = index + 1
	clearVoice(voice)
	setChildren(oNoteObjs, newNoteObjs, 'noteObjects')
	return retval

def insertPatternAtIndex(voice, insertIndex, duration, pattern):
	print('insertPatternAtIndex: insertIndex, duration, len(pattern):', insertIndex, duration, len(pattern))
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newNoteObjs = []
	pos = 0.0
	for obj in noteObjs:
		newNoteObjs.append(obj.cloneNode(deep = True))
	patternRev = list(reversed(pattern))

	pos = 0
	for objPat in patternRev:
		obj = objPat.cloneNode(deep = True)
		dura = getObjDuration(obj)
		if pos < duration - 0.001:
			odrawObjects, drawObjs = getChildren(obj, 'drawObjects')
			for drawObj in drawObjs:
				bracket = drawObj.gotoChild('bracket', False)
				if bracket is None:
					odrawObjects.removeChild(drawObj)
			group = obj.gotoChild('group', False)
			if group != None:
				obj.removeChild(group)
		print('insertPatternAtIndex - inserting object '+ obj.localName + ' at index '+str(insertIndex))
		newNoteObjs.insert(insertIndex, obj)
		pos = pos + dura
	clearVoice(voice)
	setChildren(oNoteObjs, newNoteObjs, 'noteObjects')

def hideVoiceObjsInStaff(staff, index):
	ovoices, voices = getChildren(staff, 'voice')
	voiceID = 0
	for voice in voices:
		if voiceID == index:
			oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
			for obj in noteObjs:
				if obj.localName in ['chord', 'rest']:
					display = obj.gotoChild('display', True)
					display.setAttribute('invisible', 'true')
			setChildren(oNoteObjs, noteObjs, 'noteObjects')
		voiceID = voiceID + 1
	
def copyVoiceObjsOnly(staff):
	newVoice(staff, False)
	ovoices, voices = getChildren(staff, 'voice')
	src_voice = voices[0]
	dest_voice = voices[1]
	copyVoice(src_voice, dest_voice)	
	odest_noteObjs, dest_noteObjs = getChildren(dest_voice, 'noteObjects')
	for obj in dest_noteObjs:
		drawObjs = obj.gotoChild('drawObjects', False)
		if drawObjs != None:
			obj.removeChild(drawObjs)
		group = obj.gotoChild('group', False)
		if group != None:
			obj.removeChild(group)

def copyVoice(src_voice, dest_voice):
	clearVoice(dest_voice)
	osrc_noteObjs, src_noteObjs = getChildren(src_voice, 'noteObjects')
	odest_noteObjs, dest_noteObjs = getChildren(dest_voice, 'noteObjects')
	numObjs = len(src_noteObjs)

	for o in range(0, numObjs):
		obj = src_noteObjs[o].cloneNode(deep = True)
		dest_noteObjs.append(obj)
	setChildren(odest_noteObjs, dest_noteObjs, 'noteObjects')

def copyStaff(src_staff, dest_staff):
	clearStaff(dest_staff)
	osrc_voices, src_voices = getChildren(src_staff, 'voice')
	odest_voices, dest_voices = getChildren(dest_staff, 'voice')
	numVoices = len(src_voices)
	for i in range(0, numVoices):
		src_voice = src_voices[i]
		dest_voice = dest_voices[i]
		copyVoice(src_voice, dest_voice)
	setChildren(odest_voices, dest_voices, 'voices')

def clearVoice(voice):
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newObjs = []
	for noteObj in noteObjs:
		name = noteObj.localName
		print(name)
		oNoteObjs.removeChild(noteObj)
	setChildren(oNoteObjs, newObjs, 'noteObjects')

# 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 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 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 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 getPauseMark(noteObj):
	texts = getSimpleTexts(noteObj)
	pauseBegin = False
	pauseEnd = False
	for text in texts:
		if text.lower() == 'p+':
			pauseBegin = True
		elif text.lower() == 'p-':
			pauseEnd = True
	if pauseEnd:
		return '-'
	elif pauseBegin:
		return '+'
	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, excludePosRanges, pos)
	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 getExcludeMark(noteObj):
	duration = getObjDuration(noteObj)
	if duration is not None:
		simpleTexts = getSimpleTexts(noteObj)
		text = getChordSym(noteObj)
		if text is not None and text != '':
			if simpleTexts.count(')') > 0:
				return '-'
			elif simpleTexts.count('(') > 0:
				return 'x'
	return ''
	

def getChordOrStep(noteObj, excludePosRanges, pos):
	exc = getItemAtPos(excludePosRanges, pos)
	inc = hasItemAtPos(excludePosRanges, pos)
	if type(exc) is list and len(exc) > 0:
		exc = exc[0]
	if type(inc) is list and len(inc) > 0:
		inc = inc[0]
	if exc == 'x' or inc == '-':
		return ''
	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:
					if setIgnoreUnknownChords:
						return ''
					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 getBarline(noteObj):
	if noteObj.localName == 'barline':
		typ = 'b'
		try:
			typ = noteObj.getAttribute('type')
		except:
			pass
		if typ is None or typ == '':
			typ = 'beat'
		return typ
	return ''

def getExcludePositions(staff, countOnly = False):
	return getItemPositions(getExcludeMark, staff, countOnly)

def getChordPositions(staff, excludePosRanges, countOnly = False):
	return getItemPositions(getChordOrStep, staff, countOnly, excludePosRanges)

def getPauseMarkPositions(staff, countOnly = False):
	return getItemPositions(getPauseMark, 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 getBarlinePositions(staff, countOnly = False):
	return getItemPositions(getBarline, staff, countOnly)

def getGenderHintPositions(staff, keyPosRanges, excludePosRanges, globalFretIndexPosRanges, globalPitchIndexPosRanges, countOnly = False):
	return getItemPositions(getGenderHint, staff, countOnly, keyPosRanges, excludePosRanges, globalFretIndexPosRanges, globalPitchIndexPosRanges)

def getMajorKeyPositions(staff, countOnly = False):
	return getItemPositions(getKeyMajor, staff, countOnly)

def getMinorKeyPositions(staff, countOnly = False):
	return getItemPositions(getKeyMinor, staff, countOnly)

def getGenderPositions(staff, countOnly = False):
	return getItemPositions(getGender, staff, countOnly)

def tidyRanges(itemPositionRanges):
	# Filter out zero time ranges
	posRanges = []
	for itemPosRange in itemPositionRanges:
		(begin, end), item = itemPosRange
		if begin is None:
			begin = 0.0
			itemPosRange = ((begin, end), item)
		if abs(end-begin) > 1/256:
			posRanges.append(itemPosRange)

	# Fusion of splitted time ranges on same item
	retVal = []
	orgPosRange = None
	posRangeID = 0
	(l_begin, l_end), l_item = posRanges[-1]
	lastPosRange = (l_end, l_end), l_item
	posRanges.append(lastPosRange)
	for posRange in posRanges:
		(begin, end), item = posRange
		if orgPosRange is not None:
			(o_begin, o_end), o_item = orgPosRange
			if o_item != item or posRangeID >= len(posRanges)-1:
				thisEnd = begin
				if posRangeID >= len(posRanges)-1:
					thisEnd = end
				retVal.append(((o_begin, thisEnd), o_item))
				orgPosRange = posRange
		else:
			orgPosRange = posRange
		posRangeID = posRangeID + 1
	if retVal == []:
		retVal = posRanges
	return retVal

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 setLayoutClef(layout, clef):
	notation = layout.gotoChild('notation', True)
	notation.setAttribute('defaultClef', clef)

def setSound(layout, instr, volume, sample, genericSound):
	sound = layout.gotoChild('sound', True)
	sound.setAttribute('instr', str(instr))
	sound.setAttribute('volume', str(volume))
	sound.setAttribute('sample', sample)
	sound.setAttribute('genericSound', genericSound)

def setInstrumentProperty(layout, tag, value):
	instrument = layout.gotoChild('instrument', True)
	names = instrument.getElementsByTagName(tag)
	for name in names:
		instrument.removeChild(name)
	instrument.setAttribute(tag, value)
	contentEl = doc.createElement(tag)
	textName = doc.createTextNode(value)
	contentEl.appendChild(textName)
	instrument.appendChild(contentEl)

def timeSignToPauses(staff, time = '4/4'):
	timeSign = time
	ovoices, voices = getChildren(staff, 'voice')
	for voice in voices:
		orgVoice = voice.cloneNode(deep = True)
		timeSign = collapseVoiceDurationsToPause(voice, time, timeSign)
		oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
		# Remove Volta Brackets
		for noteObj in noteObjs:
			oDrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
			if drawObjs is not None:
					for drawObj in drawObjs:
						volta = drawObj.gotoChild('volta', False)
						if volta is not None:
							drawObj.removeChild(volta)
		fixVoltasInVoice(orgVoice, voice)
	return timeSign

def timeSignToDuration(time):
	frac = time.split('/')
	return float(frac[0])/float(frac[1])

def getPausesForTimeSign(pauseObjs, duration, timeSign):
	num, denum = timeSign.split('/')
	block = []
	tsDuration = getDurationByBase(timeSign)
	if abs(tsDuration-duration) < 0.002:
		for i in range(0, int(num)):
			obj = pauseObjs[0].cloneNode(deep = True)
			duration = obj.gotoChild('duration', False)
			duration.setAttribute('base', '1/'+denum)
			try:
				duration.removeAttribute('dots')
			except:
				pass
			block.append(obj)
		return block
	return pauseObjs

def getCollapsedPause(pauseObj, duration):
	fact = 1.0
	maxDots = 3
	maxPart = 128
	newNoteBase = '1/1'
	newNoteDots = 0
	newNoteDuration = 0
	maxLoops = 20
	while int(newNoteBase.split('/')[1]) <= maxPart and newNoteDots <= maxPart and abs(newNoteDuration - duration) >= 1.0/maxPart and maxLoops > 0:
		newNoteDuration = getDurationByBase(newNoteBase, newNoteDots, newNoteDuration)
		if newNoteDuration > duration:
			newNoteBase = newNoteBase.split('/')
			newNoteBase = newNoteBase[0]+'/'+str(int(newNoteBase[1])*2)
		elif newNoteDuration < duration:
			if newNoteDots < maxDots:
				newNoteDots = newNoteDots + 1
		maxLoops = maxLoops - 1

	print('getCollapsedPause - dura / dura:', newNoteDuration, duration)
	newNote = pauseObj.cloneNode(deep = True)
	duration = newNote.gotoChild('duration', False)
	if duration is not None:
		duration.setAttribute('base', newNoteBase)	
		if newNoteDots > 0:
			duration.setAttribute('dots', str(newNoteDots))
		else:
			try:
				duration.removeAttribute('dots')
			except:
				pass
	return newNote

def collapseVoiceDurationsToPause(voice, time = '4/4', timeSign = None):
	timeDura = timeSignToDuration(time)
	oobjs, objs = getChildren(voice, 'noteObjects')
	numObjs = len(objs)
	orgPos = 0.0
	beatPos = timeSignToDuration(time)
	pos = 0.0
	block = []
	newObjs = []
	
	for i in range(0, numObjs):
		obj = objs[i]

		duration = getObjDuration(obj)

		if obj.localName == 'timeSign':
			time = obj.getAttribute('time')
			timeDura = timeSignToDuration(time)
			print('collapseVoiceDurations - updated time to (frag, dura):', time, timeDura)
			beatPos = orgPos + timeDura

		if pos >= orgPos - 0.001 and obj.localName in ['chord', 'rest']:
			print('collapseVoiceDurations - beat pos from/to:', orgPos, orgPos + timeDura)
			orgPos = orgPos + timeDura
			beatPos = orgPos + timeDura

		print('collapseVoiceDurations - pos is now:', pos)
		if duration is not None:
			pos = pos + duration
		block.append(obj)
		beatBreak = False
		if obj.localName == 'barline':
			if abs(orgPos - pos) > 0.001:
				beatBreak = True
			orgPos = pos
		if pos >= orgPos - 0.001 or i+1 >= numObjs or beatBreak:   # why the i+1?
			blockDuration = 0.0
			for blkObj in block:
				objDura = getObjDuration(blkObj)
				if objDura is not None:
					blockDuration = blockDuration + objDura
			print('collapseVoiceDurations - block dura:', blockDuration)
			print('collapseVoiceDurations - collapsing '+str(len(block))+' noteObjects, timeSign:', time)
			if timeSign is None:
				newBlock = [getCollapsedPause(block[0], blockDuration)]
			else:
				newBlock = getPausesForTimeSign(block, blockDuration, time)
			if newBlock[0].localName in [ 'rest' ]:
				if beatBreak:
					if len(newBlock) > 0:
						try:
							addText(newBlock[0], setInitialPatPart, x=0, y=float(setPositionPat), height=int(setSizePat))
						except:
							pass
				else:
					if len(newBlock) > 0:
						try:
							addText(newBlock[0], setInitialPatFull, x=0, y=float(setPositionPat), height=int(setSizePat))
						except:
							pass
			newObjs.extend(newBlock)
			if beatBreak and i != 1:
				newObjs.append(obj)
			block = []
	clearVoice(voice)
	for obj in newObjs:
		addNote(voice, obj)
	return time

def fixVoltasInVoice(orgVoice, newVoice):
	voltaNotes = []
	voltaDuras = []
	voltaDrawObjs = []
	currVoltaDrawObj = None
	voltaStart = None
	voltaEnd = None
	voltaList = []
	pos = 0.0

	# Source Voice
	oNoteObjs, noteObjs = getChildren(orgVoice, 'noteObjects')
	numObjs = len(noteObjs)
	for i in range(0, numObjs+1):
		if i < numObjs:
			obj = noteObjs[i]
		else:
			obj = None
		j = i + 1

		# Get Volta Information
		if obj is not None and obj.localName in [ 'chord', 'rest' ]:
			oDrawObjs, drawObjs = getChildren(obj, 'drawObjects')
			for drawObj in drawObjs:
				volta = drawObj.gotoChild('volta', False)
				if volta is not None:
					currVoltaDrawObj = drawObj
					basic = drawObj.gotoChild('basic', False)
					noteRange = 0
					if basic is not None:
						try:
							noteRange = int(basic.getAttribute('noteRange'))
						except:
							pass
						rangeDura = 0.0
						for j in range(i, min(i+noteRange+1, numObjs)):
							if noteObjs[j] is not None and noteObjs[j].localName in [ 'chord', 'rest' ]:
								jDura = getObjDuration(noteObjs[j])
								print('fixVoltasInVoice - volta dura:', jDura)
								rangeDura = rangeDura + jDura
						print('fixVoltasInVoice - volta rangeDura:', rangeDura)
						voltaStart = pos
						voltaEnd = pos + rangeDura
						voltaDuras.append((voltaStart, rangeDura))
						voltaDrawObjs.append(drawObj.cloneNode(deep = True))
			if currVoltaDrawObj is not None:
				voltaNotes.append(obj)

		if currVoltaDrawObj and pos >= voltaEnd - 0.001:
			print('fixVoltasInVoice - voltaNotes:', voltaNotes)
			currVoltaDrawObj = None
			voltaList.append(voltaNotes)
			voltaNotes = []
			
		if obj is not None and obj.localName in [ 'chord', 'rest' ]:
			pos = pos + getObjDuration(obj)

	pos = 0.0
	currVoltaDrawObj = None
	voltaEnd = None
	voltaID = 0
	targetVoltaNotes = []

	# Target Voice
	oNoteObjs, noteObjs = getChildren(newVoice, 'noteObjects')
	numObjs = len(noteObjs)
	for i in range(0, numObjs+1):
		if i < numObjs:
			obj = noteObjs[i]
		else:
			obj = None
		j = i + 1

		# Get Volta Information
		if obj is not None and obj.localName in [ 'chord', 'rest' ]:
			print('fixVoltasInVoice - voltaID', voltaID)
			if len(voltaDuras) > voltaID:
				voltaStart = voltaDuras[voltaID][0]
				if voltaStart is not None and pos >= voltaStart - 0.001:
					objDrawObjects = obj.gotoChild('drawObjects', True)
					objDrawObjects.appendChild(voltaDrawObjs[voltaID])
					currVoltaDrawObj = voltaDrawObjs[voltaID]
					voltaEnd = pos + voltaDuras[voltaID][1]
					print('fixVoltasInVoice - target volta duration:', voltaDuras[voltaID])
					voltaID = voltaID + 1

		if currVoltaDrawObj is not None:
			targetVoltaNotes.append(obj)

		if obj is not None and obj.localName in [ 'chord', 'rest' ]:
			pos = pos + getObjDuration(obj)

			if currVoltaDrawObj and pos >= voltaEnd - 0.001:
				print('fixVoltasInVoice - targetVoltaNotes:', targetVoltaNotes)
				voltaNoteCount = max(0, len(targetVoltaNotes)-1)
				print('fixVoltasInVoice - setting volta\'s noteRange to', voltaNoteCount)
				currVoltaBasic = currVoltaDrawObj.gotoChild('basic', True)
				if currVoltaBasic:
					currVoltaBasic.setAttribute('placement', 'auto')
					currVoltaBasic.setAttribute('noteRange', str(voltaNoteCount))
				currVoltaDrawObj = None
				targetVoltaNotes = []

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

acaChords = AcaChords()
acaChords.max_tone = 'B4'
clefList = [('treble', 'Violin'), ('bass', 'Bass'), ('N2', 'Kein'), ('alto', 'Alt'), ('tenor', 'Tenor'), ('C1', 'Sopran'), ('C2', 'Mezzosopran'),('C5', 'Bariton'), ('F3', 'Bariton F')]
estHasPatternStaff = False
config = Config()

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 msgFileNotFound(patternFile):
	messageBox('Pattern-Datei', 'Die angegebene Pattern-Datei ('+patternFile+') konnte nicht gefunden werden.')

def msgFileIsEmpty():
	messageBox('Pattern-Datei', 'Es ist keine Patterndatei angegeben.')

def msgPatternOutOfBeat(patDuration, duration, name):
	messageBox('Falsche Taktart', 'Das eingesetzte Pattern "'+str(name)+'" mit der Länge '+str(patDuration)+' passt nicht in den Takt mit der Länge von '+str(duration))

def msgSaveFirst():
	messageBox('Kein Datei-Zugriff', 'Bitte zuerst Partitur abspeichern.')

isAln = False
def analyzeScore(score):
	global estHasPatternStaff
	global numPatternStaves
	global chordsStaffName
	global markedChord
	global instrumentName
	global cursorKeys
	global cursorTime

	global isAln
	global estFretIndexFontSize	
	global estFretIndexFontColor
	global estFretIndexStaffName
	global pitchIndexColor
	global pitchIndexSize
	global pitchIndexStaffName
	global pitchIndexParsed
	global pitchIndexCount

	global setGenStaffName
	global setIgnoreUnknownChords
	global setGermanH
	global setStep7Flat
	global setUsePitchIndex

	setIgnoreUnknownChords = True
	setGermanH = False
	setStep7Flat = True
	setUsePitchIndex = True

	config.load()
	setGenStaffName = config.get('setGenStaffName', 'Pattern')

	cursor = curSelection()[0]
	print('cursor', cursor)

	numPatternStaves = 0
	chordsStaffName = '-'
	instrumentName = '-'
	markedChord = '-'
	cursorKeys = ['-', '-']
	cursorTime = '-'
	chordsStaffIDs = []
	patternStaffIDs = []

	cursor = curSelection()[0]
	print('cursor', cursor)

	estFretIndexFontColor, estFretIndexFontSize, estFretIndexStaffName, isAln = getFretColorAndSize(score)
	pitchIndexColor, pitchIndexSize, pitchIndexStaffName, pitchIndexParsed = getColorAndSizeForKeyword(score, u'Pitch')
	pitchIndexCount = getPitchIndexCount(score, pitchIndexStaffName)

	currTime = ''
	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')
			numChordNotes = 0
			hasChords = False
			voiceID = 0
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				for obj in objs:
					time = obj.getAttribute('time')
					if time is not None and time != '':
						currTime = time

					if cursor == (systemID, staffID, voiceID, objID):
						cursorTime = currTime

					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

					oDrawObjects, drawObjects = getChildren(obj, 'drawObj')
					for drawObj in drawObjects:
						basic = drawObj.gotoChild('basic')
					objID = objID + 1
				voiceID = voiceID + 1
			if hasChords:
				chordsStaffIDs.append(staffID)
			staffID = staffID + 1
		systemID = systemID + 1

	# Examine Chord Staff
	if len(chordsStaffIDs) > 0:
		val = chordsStaffIDs[0]
		print('analyzeScore - count(val) <> len(chordsStaffIDs):', chordsStaffIDs.count(val) , len(chordsStaffIDs))
		if chordsStaffIDs.count(val) == len(chordsStaffIDs):
			layout = score.gotoChild("layout")
			ostaffLayouts, staffLayouts = getChildren(layout, 'staffLayout', forcedPlural = 'staves')
			layout = staffLayouts[val]
			chordsStaffName = layout.getAttribute('description')

	patStaves = getStavesByLayout(score, setGenStaffName)
	if len(patStaves) > 0:
		estHasPatternStaff = True
    
	if chordsStaffName == '-':
		msgNoChords()
		return

	chordStaves = getStavesByLayout(score, chordsStaffName)
	wholeChordStaff = concatStaves(chordStaves)
	wTotalTime = getStaffTotalTime(wholeChordStaff)

	wExcludePositions = getExcludePositions(wholeChordStaff)
	wExcludePosRanges = genItemPositionRanges(wExcludePositions, wTotalTime, [''])
	print('whole exclusion marks', wExcludePosRanges)

	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)

	wKeyPositions = getKeyPositions(wholeChordStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)
	print('whole keys (tidy)', tidyRanges(wKeyPosRanges))

	wGenderHintPositions = getGenderHintPositions(wholeChordStaff, wKeyPosRanges, wExcludePosRanges, wGlobalFretIndexPosRanges, wPitchIndexPosRanges)
	wGenderHintPosRanges = genItemPositionRanges(wGenderHintPositions, wTotalTime, [])
	print('whole gender hint', wGenderHintPosRanges)
	print('whole gender hint (tidy)', tidyRanges(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
	
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 csArp(score):
	global estHasPatternStaff

	timeSign = '4/4'
	print('datadir:', getPersonalDataDir())

	if chordsStaffName == '-':
		msgNoChords()
		return
	
	patStaves = getStavesByLayout(score, setGenStaffName)
	if len(patStaves) > 0:
		estHasPatternStaff = True
	else:
		estHasPatternStaff = False

	layout = score.gotoChild("layout")
	ostaffLayouts, staffLayouts = getChildren(layout, 'staffLayout', forcedPlural = 'staves')

	for staffLayout in staffLayouts:
		description = staffLayout.getAttribute('description')
		if description == chordsStaffName:
			patternLayout = staffLayout.cloneNode(deep=True)
		#if description == setGenStaffName:
			#patternLayout = staffLayout.cloneNode(deep=True)

	patternLayout.removeAttribute('description')
	patternLayout.setAttribute('description', setGenStaffName)
	patternLoNotation = patternLayout.gotoChild('notation')
	patternLoBarlines = patternLoNotation.gotoChild('barlines')
	patternLoBarlines.setAttribute('mode', 'internal')

	setInstrumentProperty(patternLayout, 'name', setInstrumentName)
	setInstrumentProperty(patternLayout, 'abbrev', '')
	if setPatternStaffCharacter == 1:
		setSound(patternLayout, 127, 80, 'MIDI-Schlagzeugkanal', 'percussion.midi')
		setLayoutClef(patternLayout, 'P3')
	else:
		setLayoutClef(patternLayout, clefList[setClef][0])
	reCreate = False
	if estHasPatternStaff:
		reCreate = True
	else:
		staffLayouts.extend([patternLayout])
	setChildren(ostaffLayouts, staffLayouts, 'staves')

	topStaffExtraDistances = []
	topStaves = getTopStaves(score)
	for topStaff in topStaves:
		topStaffExtraDistances.append(getStaffDistances(topStaff))
	print('topStaves:', topStaves)	
	print('topStaffExtraDistances:', topStaffExtraDistances)	

	if chordsStaffName == '-':
		msgNoChords()
		return

	chordStaves = getStavesByLayout(score, chordsStaffName)
	wholeChordStaff = concatStaves(chordStaves)
	wTotalTime = getStaffTotalTime(wholeChordStaff)

	wExcludePositions = getExcludePositions(wholeChordStaff)
	wExcludePosRanges = genItemPositionRanges(wExcludePositions, wTotalTime, [''])
	print('whole exclusion marks', wExcludePosRanges)

	wChordPositions = getChordPositions(wholeChordStaff, wExcludePosRanges)
	wChordPosRanges = genItemPositionRanges(wChordPositions, wTotalTime, [])
	print('whole chords positions', wChordPositions)
	print('whole chords', wChordPosRanges)

	wPauseMarkPositions = getPauseMarkPositions(wholeChordStaff)
	wPauseMarkPosRanges = genItemPositionRanges(wPauseMarkPositions, wTotalTime, ['-'])
	print('whole pause marks', wPauseMarkPosRanges)

	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)

	wKeyPositions = getKeyPositions(wholeChordStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)
	print('whole keys (tidy)', tidyRanges(wKeyPosRanges))

	wGenderHintPositions = getGenderHintPositions(wholeChordStaff, wKeyPosRanges, wExcludePosRanges, wGlobalFretIndexPosRanges, wPitchIndexPosRanges)
	wGenderHintPosRanges = genItemPositionRanges(wGenderHintPositions, wTotalTime, [])
	print('whole gender hint', wGenderHintPosRanges)
	print('whole gender hint (tidy)', tidyRanges(wGenderHintPosRanges))

	wBarlinePositions = getBarlinePositions(wholeChordStaff)
	wBarlinePosRanges = genItemPositionRanges(wBarlinePositions, wTotalTime, [])
	print('whole barlines', wBarlinePosRanges)

	osystens, systems = getChildren(score, 'system')
	chord = []
	pos = 0.0
	oldPos = 0.0
	timeSign = '4/4'
	systemID = 0
	lastKey = None
	lastPat = None
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		newStaves = []
		newStaff = None
		patternStaff = None
		oldPos = pos
		for staff in staves:
			if staff.getAttribute('layout') == setGenStaffName:
				patternStaff = staff
			if staff.getAttribute('layout') == chordsStaffName:
				newStaff = staff.cloneNode(deep=True)
				staffName = newStaff.getAttribute('layout')
				print("staffName", staffName)
				if staffName is not None:
					newStaff.removeAttribute('layout')
					newStaff.setAttribute('layout', setGenStaffName)
				dummy2 = 0
				mainVoice = None
				ovoices, voices = getChildren(newStaff, 'voice')
				for voice in voices:
					if dummy2 == 0:
						mainVoice = voice
						patternVoice = mainVoice.cloneNode(deep=True)
					dummy2 = dummy2 + 1

				totalTime = getStaffTotalTime(staff)
				chordPositions = getChordPositions(staff, wExcludePosRanges)
				chordPosRanges = genItemPositionRanges(chordPositions, totalTime, chord)
				for voice in voices:
					ovoices.removeChild(voice)

					print('init:', chord)
					if len(chordPosRanges) > 0:
						chord = chordPosRanges[-1][1]

				print('pos ranges:', chordPosRanges)

				clearVoice(patternVoice)
				if setPatternStaffCharacter == 0:
					addNote(patternVoice, objClefSign(clef=clefList[setClef][0]))
				else:
					# Drum Pattern Case
					addNote(patternVoice, objClefSign(clef="P3"))
				oobjs, objs = getChildren(mainVoice, 'noteObjects')
				numObjs = len(objs)
				voltaDrawObj = None
				for i in range(0, numObjs):
					obj = objs[i]
					dura = getObjDuration(obj)
					duration = obj.gotoChild('duration', False)
					oDrawObjs, drawObjs = getChildren(obj, 'drawObjects')
					chd = getItemAtPos(wChordPosRanges, pos)
					tpitidx = getItemAtPos(wPitchIndexPosRanges, pos)
					pitidx = 0
					try:
						pitidx = int(tpitidx[0])
					except:
						pass
					print('chd:', chd)
					print('pitch, tpitch:', pitidx, tpitidx)
					if type(chd) is list:
						chd = chd[0]
					chd = chd.split('[')[0]
					if len(chd) > 0:
						if chd[0].upper() in ['I', 'V']:
							key = getItemAtPos(wKeyPosRanges, pos)
							keys = acaChords.get_keys_from_circle(int(key[0]))
							if setAutoGender:
								gender = getItemAtPos(wGenderHintPosRanges, pos)
								if len(gender)>0:
									gender = gender[0]
								else:
									gender = ''
								if gender == 'major':
									stepOne = keys[0]
								elif gender == 'minor':
									stepOne = keys[1]
								else:
									if setMajor:
										stepOne = keys[0]
									else:
										stepOne = keys[1]
							elif setMajor:
								stepOne = keys[0]
							else:
								stepOne = keys[1]
							chd_chd = chd
							extra = ''
							if chd.find('<altslash>') > 0:
								chd_chd = chd.split('<')[0]
								chd_bass = chd.split('>')[1]
								extra = '<altslash>' + chd_bass
							chd = acaChords.step_to_chord(chd_chd, stepOne, seven_flat = setStep7Flat) + extra
					if acaChords.check_chord(chd, setGermanH):
						chd = acaChords.get_transposed_chord_name(chd, pitidx, setSharp)

					if drawObjs is not None:
						for drawObj in drawObjs:
							volta = drawObj.gotoChild('volta', False)
							if volta is not None:
								voltaDrawObj = drawObj

					if dura is not None and dura > 0:
						# Slash-Chord: Chord with Bass Note
						int_setOctave = 5   # Capella Octave "4"
						if setOct4:
							int_setOctave = 4   # Real Octave 4
						try:
							int_setOctave = int(setOctave)
						except:
							pass
						if chd.find('<altslash>') > 0:
							chd_chd = chd.split('<')[0]
							chd_bass = chd.split('>')[1]
							chd_tones = acaChords.chord_to_tones_basstone(chd_chd, chd_bass, int_setOctave)
							chd = chd_chd
						else:
							chd_tones = acaChords.chord_to_tones(chd,int_setOctave)
						chd_tones = acaChords.force_sign(chd_tones, setSharp)
						if getItemAtPos(wPauseMarkPosRanges, pos)[0] == '+':
							chd_tones = []
						pause = objChord([], '1/4', False, False)
						print('place pauses:', pause)
						remDuration = pause.gotoChild('duration', False)
						if remDuration is not None and duration is not None:
							hasNoDuration = remDuration.getAttribute('noDuration')
							if hasNoDuration != '':
								remDuration.removeAttribute('noDuration')
							pause.replaceChild(duration, remDuration)
						if voltaDrawObj is not None:
							noteDrawObjs = pause.gotoChild('drawObjects', True)
							noteDrawObjs.appendChild(voltaDrawObj)
							voltaDrawObj = None
						addNote(patternVoice, pause)
					elif obj.localName == 'keySign':
						if setGlobalSign and setPatternStaffCharacter == 0:
							addNote(patternVoice, obj.cloneNode(deep=True))
					elif obj.localName not in ['clefSign', 'chord', 'rest']:
						addNote(patternVoice, obj.cloneNode(deep=True))
					if dura is not None:
						pos = pos + dura

				setChildren(ovoices, [patternVoice], 'voices')
				timeSign = timeSignToPauses(newStaff, timeSign)
				newStaves.append(newStaff)
		# create new staves
		if reCreate:
			print('createChordStaves - changing staff')
			if patternStaff is not None and newStaff is not None and setGenPatternStaffOrga == 0:
				print('createChordStaves - recreating staff')
				print('createChordStaves - newStaff:', newStaff)
				print('createChordStaves - patternStaff:', patternStaff)
				print('createChordStaves - setGenPatternStaff:', setGenPatternStaffOrga)
				clearStaff(patternStaff)
				copyVoiceObjsOnly(newStaff)
				hideVoiceObjsInStaff(newStaff, 0)
				copyStaff(newStaff, patternStaff)
				setChildren(ostaves, staves, 'staves')
			if patternStaff is not None and patScore is not None:
				timeSign, lastKey, lastPat = fillPatternsInStaff(patternStaff, lastKey, lastPat, oldPos, wKeyPosRanges, wChordPosRanges, wGenderHintPosRanges, wPitchIndexPosRanges, timeSign)
		else:
			print('createChordStaves - creating staff')
			top = topStaffExtraDistances[min(systemID+1, len(systems)-1)][0]
			try:
				setStaffDistances(newStaff, top + int(setDistAdditional), 0)
			except:
				pass
			for newStaff in newStaves:
				copyVoiceObjsOnly(newStaff)
				hideVoiceObjsInStaff(newStaff, 0)
			staves.extend(newStaves)
			setChildren(ostaves, staves, 'staves')
		systemID = systemID + 1
	
def getPatternByName(score, name, get_voice = 1, get_staff = None):
	timeSign = '4/4'
	systemID = 0
	pos = -0.0001
	startPos = None
	endPos = None
	text = None
	patKeySign = '0'
	patStartPos = None
	patEndPos = None
	patStaff = get_staff
	patSystem = None

	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		savePosSystem = pos
		keySign = '0'
		for staff in staves:
			pos = savePosSystem
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			savePosVoice = pos
			for voice in voices:
				pos = savePosVoice
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				for obj in objs:
					if obj.localName == 'keySign':
						keySign = obj.getAttribute('fifths')
					if obj.localName == 'timeSign':
						timeSign = obj.getAttribute('time')
						print('getPatternByName (find step): setting timeSign to:',timeSign)
						timeDura = timeSignToDuration(timeSign)
						if startPos is None:
							startPos = pos
						endPos = startPos + timeDura
					if endPos is None:
						timeDura = timeSignToDuration(timeSign)
						startPos = pos
						endPos = pos + timeDura
					dura = getObjDuration(obj)
					simpleTexts_full = getSimpleTexts(obj)
					simpleTexts = []
					for simpleText_full in simpleTexts_full:
						simpleTexts.append(simpleText_full.split(' ')[0])
					if name in simpleTexts:
						text = name
						print('getPatternByName (find step): found',text,'at pos',pos,', timeSign=',timeSign)
					if dura is not None:
						pos = pos + dura
					if pos > endPos - 0.001 or obj.localName == 'barline':
						if text is not None:
							patStartPos = startPos
							patEndPos = endPos
							patKeySign = keySign
							patSystem = systemID
							if patStaff is None:
								patStaff = staffID
							text = None
						endPos = None
						startPos = None
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
		
	systemID = 0
	pos = -0.0001
	endPos = None
	tempBlock = []
	patBlock = []

	print('getPatternByName: patKeySign, patStartPos, patEndPos, patStaff, patSystem', patKeySign, patStartPos, patEndPos, patStaff, patSystem)

	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		savePosSystem = pos
		for staff in staves:
			pos = savePosSystem
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			savePosVoice = pos
			for voice in voices:
				pos = savePosVoice
				oObjs, objs = getChildren(voice, 'noteObjects')
				objID = 0
				thisBeat = False
				for obj in objs:
					if obj.localName == 'timeSign':
						timeSign = obj.getAttribute('time')
						print('getPatternByName: setting timeSign to:',timeSign)
						timeDura = timeSignToDuration(timeSign)
						endPos = pos + timeDura
					if obj.localName == 'chord':
						oheads, heads = getChildren(obj, 'head')
						for head in heads:
							pitch = head.getAttribute('pitch')
							print('pitch:', pitch)
					if obj.localName in ['chord', 'rest']:
						tempBlock.append(obj)
					if endPos is None:
						timeDura = timeSignToDuration(timeSign)
						endPos = pos + timeDura
					dura = getObjDuration(obj)
					if dura is not None:
						pos = pos + dura
					if patStartPos is not None and patEndPos is not None:
						if pos>patStartPos+0.001 and pos<patEndPos-0.001:
							thisBeat = True
					if pos > endPos - 0.001 or obj.localName == 'barline':
						if thisBeat and len(patBlock) == 0 and voiceID == get_voice - 1 and staffID == patStaff and systemID == patSystem:
							print('getPatternByName: extracting pattern at pos',pos,', timeSign=',timeSign)
							patBlock = tempBlock
							thisBeat = False
							patDuration = getBlockDuration(patBlock)
							print('getPatternByName: pattern has duration:',patDuration)
						tempBlock = []
						endPos = None
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	if len(patBlock) == 0:
		return None, None
	return patKeySign, patBlock


# Analyze Phase
class ScoreAnalyze(ScoreChange):
	def changeScore(self, score):
		global doc
		doc = score.parentNode
		analyzeScore(score)

# Read Note Patterns
class ScoreReadNotePatterns(ScoreChange):
	def changeScore(self, score):
		readNotePatterns(score)
		global patScore
		global docPat
		patScore = score
		docPat = score.parentNode

# 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
		csArp(score)

def beautify(text):
	return text.replace('#', '♯').replace('b', '♭')

# Plugin Dialog
def dialog():
	global config

	global setGenStaffName
	global setInstrumentName
	global setDistAdditional
	global setGlobalSign
	global setSharp
	global setIgnoreUnknownChords
	global setStep7Flat
	global setGermanH
	global setOct4
	global setMinTone
	global setMaxTone
	global setOctave
	global setClef
	global setPatternFileName
	global setPatternVoice
	global setInitialPatFull
	global setInitialPatPart
	global setSizePat
	global setPositionPat
	global setPatternStaffCharacter
	global setMajor
	global setAutoGender
	global setUsePitchIndex
	global setUseHarmonization
	global setChangeThirdsAndFifths
	global setGenPatternStaffOrga

	global editGenStaffName
	global editInstrumentName
	global editDistAdditional
	global checkGlobalSign
	global checkSharp
	global checkIgnoreUnknownChords
	global checkStep7Flat
	global checkGermanH
	global checkOct4
	global editMinTone
	global editMaxTone
	global editOctave
	global comboClef
	global editPatternFileName
	global editPatternVoice
	global editInitPatFull
	global editInitPatPart
	global editSizePat
	global editPositionPat
	global radioPatternStaffCharacter
	global radioGenPatternStaffOrga
	global checkMajor
	global checkAutoGender
	global checkUsePitchIndex
	global checkUseHarmonization
	global checkChangeThirdsAndFifths
	global checkReset

	global isAln
	global estFretIndexFontColor
	global estFretIndexFontSize
	global estFretIndexStaffName

	checkReset = CheckBox('reset', value = False)

	config.load()

	wdt = 30
	wdtRight = 39
	wdtFoffs = 8
	spacer = Label('')

	setGenStaffName = config.get('setGenStaffName', 'Pattern')
	setInstrumentName = config.get('setInstrumentName', 'Arp')
	setDistAdditional = config.get('setDistAdditional', 0)
	setGlobalSign = config.get('setGlobalSign', True)
	setSharp = config.get('setSharp', True)
	setIgnoreUnknownChords = config.get('setIgnoreUnknownChords', True)
	setStep7Flat = config.get('setStep7Flat', True)
	setGermanH = config.get('setGermanH', False)
	setOct4 = config.get('setOct4', True)
	setMinTone = config.get('setMinTone', 'C3')
	setMaxTone = config.get('setMaxTone', 'B5')
	setOctave = config.get('setOctave', '4')
	setClef = config.get('setClef', 0) # treble
	setPatternFileName = config.get('setPatternFileName', "aC-Arp-44-24-Bass-Patterns.capx")
	setPatternVoice = config.get('setPatternVoice', '1')
	setInitialPatFull = config.get('setInitialPatFull', '/')
	setInitialPatPart = config.get('setInitialPatPart', 'UT')
	setSizePat = config.get('setSizePat', '10')
	setPositionPat = config.get('setPositionPat', '-4.0')
	setPatternStaffCharacter = config.get('setPatternStaffCharacter', 0)
	setMajor = config.get('setMajor', True)
	setAutoGender = config.get('setAutoGender', True)
	setUsePitchIndex = config.get('setUsePitchIndex', True)
	setUseHarmonization = config.get('setUseHarmonization', True)
	setChangeThirdsAndFifths = config.get('setChangeThirdsAndFifths', True)
	setGenPatternStaffOrga = int(estHasPatternStaff)

	labelChordStaffName = Label('Akkord-/Stufen-Notenzeilenname', width = wdtRight / 2 + 4)
	labelEstKey = Label('Tonart (an Cursorposition)', width = wdtRight / 2 + 4)
	labelEstBase = Label('Taktart (an Cursorposition)', width = wdtRight / 2 + 4)
	fretIndexColor = Label('Fret-Index Font-Farbe', width=wdtRight / 2 + 4)
	fretIndexHeight = Label('Fret-Index Font-Größe', width=wdtRight / 2 + 4)
	fretIndexActive = Label('Fret-Index aktuell aktiv', width=wdtRight / 2 + 4)
	labelEstPitchIndexColor = Label('Pitch-Index Font-Farbe', width = wdtRight / 2 + 4)
	labelEstPitchIndexSize = Label('Pitch-Index Font-Größe', width = wdtRight / 2 + 4)
	labelEstPitchIndexNum = Label('Anzahl Pitch-Indexe', width = wdtRight / 2 + 4)
	vBoxEstLabels = VBox([labelChordStaffName, labelEstKey, labelEstBase, fretIndexColor, fretIndexHeight, fretIndexActive, labelEstPitchIndexColor, labelEstPitchIndexSize, labelEstPitchIndexNum])

	labelChordStaffNameValue = Label(': ' + chordsStaffName, width = wdtRight / 2)
	labelEstKeyValue = Label(': ' + beautify(cursorKeys[0])+' / '+beautify(cursorKeys[1]), width = wdtRight / 2)
	labelEstBaseValue = Label(': ' + cursorTime, width = wdtRight / 2)
	fColor = '-'
	fSize = '-'
	isConv = '-'
	if estFretIndexFontColor != '':
		fColor = '#'+estFretIndexFontColor
		fSize = str(estFretIndexFontSize)+' px'
		if isAln:
			isConv = "Ja"
		else:
			isConv = "Nein"
	fretIndexColorValue = Label(': '+fColor, width=wdtRight / 2)
	fretIndexHeightValue = Label(': '+fSize, fg = estFretIndexFontColor, width=wdtRight / 2)
	fretIndexActiveValue = Label(': '+isConv, width=wdtRight / 2)
	colorName = "#"+pitchIndexColor
	if pitchIndexColor == '':
		colorName = '-'
	labelEstPitchIndexColorValue = Label(': ' + colorName, width = wdtRight / 2)
	fontSize = str(pitchIndexSize)
	if pitchIndexSize == 0:
		fontSize = '-'
	labelEstPitchIndexSizeValue = Label(': ' + fontSize, width = wdtRight / 2)
	if pitchIndexColor == '':
		colorName = '-'
		printedPitchIndexCount = '-'
	else:
		printedPitchIndexCount = str(pitchIndexCount)
	labelEstPitchIndexNumValue = Label(': ' + str(printedPitchIndexCount), width = wdtRight / 2)
	vBoxEstValues = VBox([labelChordStaffNameValue, labelEstKeyValue, labelEstBaseValue, fretIndexColorValue, fretIndexHeightValue, fretIndexActiveValue, labelEstPitchIndexColorValue, labelEstPitchIndexSizeValue, labelEstPitchIndexNumValue])

	hBoxEstProperties = HBox([vBoxEstLabels, vBoxEstValues], text = 'Ermittelte Eigenschaften aus Notenzeile mit Akkord-/Stufensymbolen')

	checkReset = CheckBox('EINSTELLUNGEN ZURÜCKSETZEN', value=False, width=wdtRight)
	checkMajor = CheckBox('Globaler Tonartencharakter (Gender) = Dur', value=setMajor, width=wdtRight)
	checkAutoGender = CheckBox('Globalen Tonartencharakter automatisch ermitteln', value=setAutoGender, width=wdtRight)
	checkUsePitchIndex = CheckBox('Pitch-Index (intr.  Tonartenkorr.) berücksichtigen', value=setUsePitchIndex, width=wdtRight)
	checkUseHarmonization = CheckBox('Pattern-Harmonisierung nach Import', value=setUseHarmonization, width=wdtRight)
	checkChangeThirdsAndFifths = CheckBox('Terz- und Quinttöne aus Pattern an Akkorde anpassen', value=setChangeThirdsAndFifths, width=wdtRight+4)
	optGenPatternStaff = [ '1. Leere anlegen oder vorhandene reinitialisieren', '2. Vorhandene mit Pattern füllen' ]
	radioGenPatternStaffOrga = Radio(optGenPatternStaff, text = '2-stufige Organisation der (erzeugten) Pattern-Notenzeile', value = setGenPatternStaffOrga)
	pluginBoxV= VBox([checkReset, checkAutoGender, checkMajor, checkUsePitchIndex, checkUseHarmonization, checkChangeThirdsAndFifths, spacer, radioGenPatternStaffOrga, spacer])
	pluginBox = HBox([pluginBoxV], text = 'Plugin-Anwendung')

	labelPatternFileName = Label('Dateiname', width = wdt / 6 + wdtFoffs)
	editPatternFileName = Edit(value = setPatternFileName, width = wdt - 2)
	hBoxPatternFileName = HBox([labelPatternFileName, editPatternFileName], width = wdt)

	labelPatternVoice = Label('Importieren von Pattern-Stimme', width = wdt / 2 + wdtFoffs + 12)
	editPatternVoice = Edit(value = setPatternVoice, width = wdt / 2 - 9)
	hBoxPatternVoice = HBox([labelPatternVoice, editPatternVoice], width = wdt/4)

	optsPatternStaff = ['Melody-Pattern (harmoniespezifisch)', 'Drum-Pattern']
	radioPatternStaffCharacter = Radio(optsPatternStaff, text='Charakter der erzeugten Pattern-Notenzeile', value=setPatternStaffCharacter)	

	labelInitPatPart = Label('Unvollständige Takte', width = wdt / 2 + wdtFoffs - 3)
	editInitPatPart = Edit(value = setInitialPatPart, width = wdt / 2 + 4)
	hBoxInitPatPart = HBox([labelInitPatPart, editInitPatPart])
	labelInitPatFull = Label('Vollständige Takte', width = wdt / 2 + wdtFoffs - 3)
	editInitPatFull = Edit(value = setInitialPatFull, width = wdt / 2 + 4)
	hBoxInitPatFull = HBox([labelInitPatFull, editInitPatFull])
	labelSizePat = Label('Font-Größe (px)', width = wdt / 2 + wdtFoffs + 10)
	editSizePat = Edit(value = setSizePat, width = wdt / 2 - 9 )
	hBoxSizePat = HBox([labelSizePat, editSizePat])
	labelPositionPat = Label('Position (relativ zu Notenzeile, +: below)', width = wdt / 2 + wdtFoffs + 10)
	editPositionPat = Edit(value = setPositionPat, width = wdt / 2 - 9 )
	hBoxPositionPat = HBox([labelPositionPat, editPositionPat])
	vBoxInitialPatName = VBox([hBoxInitPatPart, hBoxInitPatFull, hBoxSizePat, hBoxPositionPat])
	hBoxInitialPatName = HBox([vBoxInitialPatName], text = 'Initial Takt-Namen,  Größe und Position')

	vBoxPropGenStaff = VBox([hBoxPatternFileName, hBoxPatternVoice, spacer, radioPatternStaffCharacter, hBoxInitialPatName]) 
	hBoxPropGenStaff = HBox([vBoxPropGenStaff], text = 'Pattern Einstellungen')


	labelGenStaffName = Label('Pattern-Notenzeilenname', width = wdt / 2 + wdtFoffs)
	editGenStaffName = Edit(value = setGenStaffName, width = wdt / 2 - wdtFoffs + 7)
	hBoxGenStaffName = HBox([labelGenStaffName, editGenStaffName], width = wdt)
	labelInstrumentName = Label('Instrumentenbezeichnung', width = wdt / 2 + wdtFoffs)
	editInstrumentName = Edit(value = setInstrumentName, width = wdt / 2 - wdtFoffs + 7)
	hBoxInstrumentName = HBox([labelInstrumentName, editInstrumentName], width = wdt)
	if estHasPatternStaff:
		hasPatternStaffText = 'ja'
	else:
		hasPatternStaffText = 'nein'
	labelEstHasPatternStaff = Label('Pattern-Notenzeile vorhanden	: ' + hasPatternStaffText, width = wdt)
	labelDists = Label('Nur bei Erst-Erzeugung der Pattern-Notenzeile:', width = wdt + 5)
	labelDistAdditional = Label('Zusätzl.  "Notenzeilen"-Abstand nach oben', width = wdt + wdtFoffs - 8)
	editDistAdditional = Edit(value = str(setDistAdditional), width = wdt / 2 - wdtFoffs)
	hBoxDistAdditional = HBox([labelDistAdditional, editDistAdditional], width = wdt)

	labelMelodyInfo1 = Label('Alle folgenden Einstellungen (in dieser Spalte) gelten nur', width = wdt + 8)
	labelMelodyInfo2 = Label('für harmonie-spezifische Melody-Patterns,  nicht für', width = wdt + 8)
	labelMelodyInfo3 = Label('Drum-Patterns:', width = wdt + 8)
	vBoxMelodyInfoText = VBox([labelMelodyInfo1, labelMelodyInfo2, labelMelodyInfo3], width = wdt + 8)
	hBoxMelodyInfoText = HBox([vBoxMelodyInfoText], text = '')
	
	checkGlobalSign = CheckBox('Globale Versetzungszeichen übernehmen (oder keine)', value=setGlobalSign, width=wdt+10)
	checkSharp = CheckBox('Lokale Vorzeichen ♯ bevorzugen', value=setSharp, width=wdt)
	checkIgnoreUnknownChords = CheckBox('Unbekannte Akk./Stufen ignorieren (oder Org.-Pattern)', value=setIgnoreUnknownChords, width=wdt+10)
	checkStep7Flat = CheckBox('Dur-Stufen VII und vii = Tonleiterposition 7♭ (oder 7)', value=setStep7Flat, width=wdt+10)
	checkGermanH = CheckBox('H als B interpretieren (→ B = B♭)', value=setGermanH, width=wdt)

	labelOctave = Label('Oktave Arpeggio-Grundton (" ")', width = wdt / 2 + wdtFoffs + 7)
	checkOct4 = CheckBox('Mittleres C ist Oktave 4 (oder in Capella Oktave 5)', value=setOct4, width=wdt + 9)
	editOctave = Edit(value = setOctave, width = wdt / 2 - wdtFoffs)
	hBoxOctave = HBox([labelOctave, editOctave], width = wdt)
	labelMinTone = Label('Tiefste Note', width = wdt / 2 + wdtFoffs + 7)
	editMinTone = Edit(value = setMinTone, width = wdt / 2 - wdtFoffs)
	hBoxMinTone = HBox([labelMinTone, editMinTone], width = wdt)
	labelMaxTone = Label('Höchste Note', width = wdt / 2 + wdtFoffs + 7)
	editMaxTone = Edit(value = setMaxTone, width = wdt / 2 - wdtFoffs)
	hBoxMaxTone = HBox([labelMaxTone, editMaxTone], width = wdt)
	labelClef = Label('Notenschlüssel', width = wdt / 2 + 7)
	comboClef = ComboBox([x[1] for x in clefList], value = setClef, width = wdt / 2 + 1)
	hBoxClef = HBox([labelClef, comboClef], width = wdt)
	vBoxPropGenArpStaff = VBox([hBoxGenStaffName, hBoxInstrumentName, labelEstHasPatternStaff, spacer, labelDists, hBoxDistAdditional, spacer, hBoxMelodyInfoText, spacer, checkGlobalSign, checkSharp, checkIgnoreUnknownChords, checkStep7Flat, checkGermanH, spacer, checkOct4, hBoxOctave, hBoxMaxTone, hBoxMinTone, hBoxClef], width = wdt)
	hBoxPropGenArpStaff = HBox([vBoxPropGenArpStaff], text = 'Einstellbare Eigenschaften für erzeugte Pattern-Notenzeile')

	labelPluginInfo1 = Label('Eine Pattern-Notenzeile (mit den Pattern Takt-Namen in', width = wdt + 10)
	labelPluginInfo2 = Label('der obersten Stimme) wird einmalig erzeugt, falls sie noch ', width = wdt + 10)
	labelPluginInfo3 = Label('nicht existiert. Bei Änderungen werden die Noteninhalte', width = wdt + 10)
	labelPluginInfo4 = Label('überschrieben. Das Löschen einer Pattern-Notenzeile', width = wdt + 10)
	labelPluginInfo5 = Label('erfolgt im Capella-Mustersystem.', width = wdt + 8)
	vBoxInfoText = VBox([labelPluginInfo1, labelPluginInfo2, labelPluginInfo3, labelPluginInfo4, labelPluginInfo5], width = wdt + 10)
	hBoxInfoText = HBox([vBoxInfoText], text = 'Pattern-Notenzeile erzeugen und löschen')

	labelMarker1 = Label('"/" Repetition (Pattern wiederholen)', width = wdt + 10)
	labelMarker2 = Label('"*" Hiding (Pattern-Name verstecken)', width = wdt + 10)
	labelMarker3 = Label('" " Invisible (Takt ohne Pattern-Name)', width = wdt + 10)
	vBoxMarkerText = VBox([labelMarker1, labelMarker2, labelMarker3], width = wdt + 10)
	hBoxMarkerText = HBox([vBoxMarkerText], text = 'Marker (für Pattern-Namen im Takt)')

	vBoxLeft = VBox([hBoxEstProperties, spacer, hBoxPropGenStaff, pluginBox])
	vBoxRight = VBox([hBoxPropGenArpStaff, hBoxInfoText, hBoxMarkerText])
	hBox = HBox([vBoxRight, spacer, vBoxLeft])
	vBox = VBox([hBox])
	dlg = Dialog('aC.Arpeggiator', 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:
		setGenStaffName = editGenStaffName.value()
		setInstrumentName = editInstrumentName.value()
		setDistAdditional = editDistAdditional.value()
		setGlobalSign = checkGlobalSign.value()
		setSharp = checkSharp.value()
		setIgnoreUnknownChords = checkIgnoreUnknownChords.value()
		setStep7Flat = checkStep7Flat.value()
		setGermanH = checkGermanH.value()
		setOct4 = checkOct4.value()
		setMinTone = editMinTone.value()
		setMaxTone = editMaxTone.value()
		setOctave = editOctave.value()
		setClef = comboClef.value()
		setGenPatternStaffOrga = radioGenPatternStaffOrga.value()
		setInitialPatFull = editInitPatFull.value()
		setInitialPatPart = editInitPatPart.value()
		setSizePat = editSizePat.value()
		setPositionPat = editPositionPat.value()
		setPatternFileName = editPatternFileName.value()
		setPatternVoice = editPatternVoice.value()
		setPatternStaffCharacter = radioPatternStaffCharacter.value()
		setMajor = checkMajor.value()
		setAutoGender = checkAutoGender.value()
		setUsePitchIndex = checkUsePitchIndex.value()
		setUseHarmonization = checkUseHarmonization.value()
		setChangeThirdsAndFifths = checkChangeThirdsAndFifths.value()
			
		config.set("setGenStaffName", setGenStaffName)
		config.set('setInstrumentName', setInstrumentName)
		config.set('setDistAdditional', setDistAdditional)
		config.set('setGlobalSign', setGlobalSign)
		config.set('setSharp', setSharp)
		config.set('setIgnoreUnknownChords', setIgnoreUnknownChords)
		config.set('setStep7Flat', setStep7Flat)
		config.set('setGermanH', setGermanH)
		config.set('setOct4', setOct4)
		config.set('setMinTone', setMinTone)
		config.set('setMaxTone', setMaxTone)
		config.set('setOctave', setOctave)
		config.set('setClef', setClef)
		config.set('setInitialPatFull', setInitialPatFull)
		config.set('setInitialPatPart', setInitialPatPart)
		config.set('setSizePat', setSizePat)
		config.set('setPositionPat', setPositionPat)
		config.set('setPatternFileName', setPatternFileName)
		config.set('setPatternVoice', setPatternVoice)
		config.set('setPatternStaffCharacter', setPatternStaffCharacter)
		config.set('setMajor', setMajor)
		config.set('setAutoGender', setAutoGender)
		config.set('setUsePitchIndex', setUsePitchIndex)
		config.set('setUseHarmonization', setUseHarmonization)
		config.set('setChangeThirdsAndFifths', setChangeThirdsAndFifths)

		print(config.dic)
		config.save()

		activeScore().registerUndo("aC.Arpeggiator")

		if setPatternFileName.startswith('/'):
			# Absolute Path
			patternFile = setPatternFileName
		else:
			# Relative Path
			patternPath = os.path.split(activeScore().pathName())[0]
			patternFile = patternPath+'/'+setPatternFileName
		patFileOpenFailed = False
		patScore = None
		if setGenPatternStaffOrga == 1:
			if setPatternFileName.strip() == '':
				msgFileIsEmpty()
				patFileOpenFailed = True
			elif not os.path.isfile(patternFile):
				if patternPath == '':
					msgSaveFirst()
				else:
					msgFileNotFound(patternFile)
					patFileOpenFailed = True
			else:
				patFile = open(patternFile)
				tempOutput = tempfile.mktemp('.capx')
				ScoreReadNotePatterns(patFile, tempOutput)
				os.remove(tempOutput)

		if not patFileOpenFailed:
			tempInput = tempfile.mktemp('.capx')
			tempOutput = tempfile.mktemp('.capx')
			activeScore().write(tempInput)
			ScoreChange(tempInput, tempOutput)
			activeScore().read(tempOutput)
			os.remove(tempInput)
			os.remove(tempOutput)
