MuseParse/classes/ObjectHierarchy/TreeClasses/MeasureNode.py
import copy
from MuseParse.classes.ObjectHierarchy.TreeClasses.BaseTree import IndexedNode, BackwardSearch, Search
from MuseParse.classes.ObjectHierarchy.TreeClasses.VoiceNode import VoiceNode
from MuseParse.classes.ObjectHierarchy.TreeClasses.OtherNodes import DirectionNode, ExpressionNode, KeyNode, ClefNode
from MuseParse.classes.ObjectHierarchy.TreeClasses import NoteNode
from MuseParse.classes.ObjectHierarchy.ItemClasses import Directions
from MuseParse import helpers
from MuseParse.classes.ObjectHierarchy.ItemClasses.Directions import OctaveShift
class MeasureNode(IndexedNode):
"""
Class representing the tree node for a measure.
# Optional inputs:
- partial: bool. Represents whether the bar is partial i.e a pickup or something
# Optional attributes:
- newSystem: boolean indicating to create a new system for this bar
- newPage: boolean indicating to create a new page for this bar
- key: Key class entry representing key sig
- meter: meter class entry representing meter/time sig
"""
def __init__(self, **kwargs):
IndexedNode.__init__(self, rules=[VoiceNode])
self.index = 0
self.barlines = {}
self.items = []
self.autoBeam = False
if "partial" in kwargs:
if kwargs["partial"] is not None:
self.partial = kwargs["partial"]
self.gap = 0
def addWrapper(self, item):
# method to add any notation that needs to wrap the whole bar
self.items.append(item)
def CalculateTransposition(self):
scale = [
'a',
'bes',
'b',
'c',
'cis',
'd',
'dis',
'e',
'f',
'fis',
'g',
'gis']
base = 3
result = base
octave_shifters = "'"
if hasattr(self.transpose, "chromatic"):
chromatic = int(self.transpose.chromatic)
result = base + chromatic
elif hasattr(self.transpose, "diatonic"):
diatonic = int(self.transpose.diatonic)
result = base + (diatonic * 2)
if result < 0:
result = (len(scale) - 1) - result
octave_shifters = ""
if result >= len(scale):
result = result - (len(scale) - 1)
octave_shifters += "'"
lettername = scale[result]
if hasattr(self.transpose, "octave"):
octave = self.transpose.octave
if octave > 0:
octave_shifters += "".join(["'" for i in range(octave)])
if octave < 0:
octave_shifters = ""
if octave < -1:
octave_shifters += "".join(
["," for i in range(abs(octave))])
return "\\transpose c' " + lettername + octave_shifters + " {"
def HandleAttributes(self):
lilystring = ""
if hasattr(self, "newSystem"):
lilystring += "\\break "
if hasattr(self, "newPage"):
lilystring += "\\pageBreak "
if hasattr(self, "clef") and self.clef is not None:
lilystring += self.clef.toLily() + " "
if hasattr(self, "key") and self.key is not None:
if not hasattr(self.key, "mode"):
self.key.mode = "major"
lilystring += self.key.toLily() + " "
if hasattr(self, "meter"):
lilystring += self.meter.toLily() + " "
if hasattr(self, "partial") and self.partial:
lilystring += "\\partial " + str(self.getPartialLength()) + " "
for item in self.items:
if not hasattr(
item,
"type") or (
hasattr(
item,
"type") and item.type != "stop"):
lilystring += item.toLily()
return lilystring
def HandleClosingAttributes(self):
lstring = ""
for item in self.items:
if hasattr(item, "type") and item.type == "stop":
lstring += item.toLily()
return lstring
def getWrapper(self, index):
if index < len(self.items):
return self.items[index]
def GetTotalValue(self):
"""Gets the total value of the bar according to it's time signature"""
value = ""
if hasattr(self, "meter"):
top_value = self.meter.beats
bottom = self.meter.type
fraction = top_value / bottom
if fraction == 1:
value = "1"
else:
if fraction > 1:
value = "1."
if fraction < 1:
if fraction >= 0.5:
fraction -= 0.5
value = "2"
if fraction == 0.25:
value += "."
return value
def AddBarline(self, item, location):
if location != "left" and location != "right" and location != "center":
if location not in self.barlines:
self.barlines[location] = []
self.barlines[location].append(item)
else:
self.barlines[location] = item
def GetBarline(self, location):
if location in self.barlines:
return self.barlines[location]
def GetLastKey(self, voice=1):
"""key as in musical key, not index"""
voice_obj = self.GetChild(voice)
if voice_obj is not None:
key = BackwardSearch(KeyNode, voice_obj, 1)
if key is not None:
return key
else:
if hasattr(self, "key"):
return self.key
else:
if hasattr(self, "key"):
return self.key
def addKey(self, item, voice=1):
if not hasattr(self, "key"):
self.key = item
else:
if self.GetChild(voice) is None:
self.addVoice(VoiceNode(), voice)
voice_obj = self.GetChild(voice)
node = KeyNode()
node.SetItem(item)
if voice_obj is not None:
voice_obj.AddChild(node)
self.index += 1
def GetLastClef(self, voice=1):
if self.GetChild(voice) is None:
self.addVoice(VoiceNode(), voice)
voice_obj = self.GetChild(voice)
if voice_obj is not None:
key = BackwardSearch(ClefNode, voice_obj, 1)
if key is not None:
return key
else:
if hasattr(self, "clef"):
return self.clef
def addClef(self, item, voice=1):
'''
method to use when adding a clef. will either add it to the node itself or add it onto the first voice's children
list
:param item:
:param voice:
:return:
'''
if not hasattr(self, "clef"):
self.clef = item
else:
voice_obj = self.GetChild(voice)
node = ClefNode()
node.SetItem(item)
if voice_obj is not None:
voice_obj.AddChild(node)
self.index += 1
def CheckDivisions(self):
children = self.GetChildrenIndexes()
divisions = self.divisions
for child in children:
voice = self.GetChild(child)
indexes = voice.GetChildrenIndexes()
for i in indexes:
note = voice.GetChild(i)
item = note.GetItem()
if item is not None:
item.divisions = divisions
def Forward(self, duration=0):
'''
method to use when forward tag is encountered in musicXML. jumps forward in the bar by <duration>
:param duration: number of beats to move forward
:return:
'''
for voice in self.GetChildrenIndexes():
voice_obj = self.getVoice(voice)
if voice_obj.GetChild(self.index) is None:
voice_obj.AddChild(NoteNode.NoteNode(duration=duration))
self.index += 1
def Backup(self, duration=0):
'''
method to use when a backup tag is encountered in musicXML. Moves back in the bar by <duration>
:param duration:
:return:
'''
total = 0
duration_total = duration * 4
children = self.GetChildrenIndexes()
notes = 0
for voice in children:
v = self.GetChild(voice)
indexes = v.GetChildrenIndexes()
if len(indexes) > 1:
indexes.reverse()
for index in indexes:
notes += 1
note = v.GetChild(index)
if hasattr(note, "duration"):
total += note.duration
if total >= duration_total:
break
gap = [
v.GetChild(i).duration for i in range(
0,
self.index -
notes) if hasattr(
v.GetChild(i),
"duration")]
previous = 0
for item in gap:
if item == previous:
self.gap -= previous
item = item / 2
self.gap += item
previous = item
#self.gap = sum([])
self.index -= notes
def addVoice(self, item=None, id=1):
if item is None:
item = VoiceNode()
self.AddChild(item, id)
if self.gap != 0:
voice = self.GetChild(id)
voice.AddChild(NoteNode.Placeholder(duration=self.gap))
self.gap = 0
def getVoice(self, key):
return self.GetChild(key)
def PositionChild(self, item, key, voice=1):
voice_obj = self.getVoice(voice)
children = voice_obj.GetChildrenIndexes()
if key in children:
start_index = key
end_index = len(children)
popped = []
for index in range(start_index, end_index):
child = voice_obj.PopChild(index)
if child is not None:
popped.append(child)
voice_obj.AddChild(item)
[voice_obj.AddChild(p) for p in popped]
def addNote(self, item, voice=1, increment=1, chord=False):
shift = 0
# get the appropriate voice
if self.getVoice(voice) is None:
self.addVoice(VoiceNode(), voice)
voice_obj = self.getVoice(voice)
last = voice_obj.GetChild(self.index - 1)
if last is not None and hasattr(last, "shift"):
shift = True
# set up a basic duration: this val will only be used for a placeholder
duration = 0
if not isinstance(
item,
NoteNode.NoteNode) and not isinstance(
item,
NoteNode.Placeholder):
# wrap the item in a node if it isn't wrapped already
if hasattr(item, "duration"):
duration = item.duration
node = NoteNode.NoteNode(duration=duration)
node.SetItem(item)
if shift != 0:
node.shift = shift
else:
node = item
if hasattr(node, "duration"):
duration = node.duration
if not chord:
try:
voice_obj.addNoteDuration(int(duration))
except:
if duration == "\\longa":
voice_obj.addNoteDuration(0.25)
if duration == "\\breve":
voice_obj.addNoteDuration(0.5)
# get whatever is at the current index
placeholder = voice_obj.GetChild(self.index)
if isinstance(
placeholder,
NoteNode.Placeholder) and not isinstance(
node,
NoteNode.Placeholder):
# if it's an empty placeholder, replace it with a note
if placeholder.duration == 0:
if hasattr(placeholder, "shift"):
children = placeholder.GetChildrenIndexes()
# this will need some recursion if we have multiple directions attached to a placeholder,
# but for now this should work.
for c in children:
child = placeholder.GetChild(c)
if child.GetItem(
).__class__.__name__ == OctaveShift.__name__:
i = placeholder.PopChild(c)
voice_obj.ReplaceChild(
self.index,
copy.deepcopy(i))
self.index += 1
if voice_obj.GetChild(self.index) == placeholder:
voice_obj.ReplaceChild(self.index, node)
elif voice_obj.GetChild(self.index) is not None:
self.PositionChild(node, self.index, voice=voice)
else:
voice_obj.AddChild(node, self.index)
if not isinstance(node, NoteNode.Placeholder):
self.index += 1
# nothing there? add our note
elif placeholder is None:
voice_obj.AddChild(node)
if not isinstance(node, NoteNode.Placeholder):
self.index += 1
else:
proposed_node = voice_obj.GetChild(self.index)
new_duration = voice_obj.GetChild(self.index).duration
if proposed_node.GetItem() is None:
if hasattr(item, "duration"):
new_duration = item.duration
if new_duration == proposed_node.duration:
node.SetItem(node.GetItem())
voice_obj.removeNoteDuration(proposed_node.duration)
voice_obj.ReplaceChild(self.index, node)
elif new_duration > proposed_node.duration:
proposed_node.SetItem(node.GetItem())
proposed_node.duration = new_duration
voice_obj.removeNoteDuration(proposed_node.duration)
elif new_duration < proposed_node.duration:
voice_obj.removeNoteDuration(proposed_node.duration)
voice_obj.removeNoteDuration(new_duration)
proposed_node.duration -= new_duration
voice_obj.addNoteDuration(proposed_node.duration)
voice_obj.addNoteDuration(new_duration)
voice_obj.AddChild(node)
if not isinstance(node, NoteNode.Placeholder):
self.index += 1
else:
self.PositionChild(node, self.index, voice=voice)
else:
# get whatever is at the current index
placeholder = voice_obj.GetChild(self.index - 1)
if placeholder.GetItem() is not None:
if hasattr(placeholder.GetItem(), "beams"):
node.GetItem().beams = placeholder.GetItem().beams
if placeholder is not None:
placeholder.AttachNote(node)
def getPartialLength(self):
""" method to calculate how much to give the "partial" indicator where a measure is a pickup
:return: str which is the lilypond bar length
"""
indexes = self.GetChildrenIndexes()
length = 0
divider = 0
total = 0
result = ""
for v in indexes:
voice = self.GetChild(v)
if voice.note_total > length:
length = voice.note_total
note_types = voice.GetAllNoteTypes()
if len(note_types) == 1:
total = length
result = str(total)
else:
note_types.sort()
total = note_types[0]
result = str(total)
previous = total
count = 0
for value in note_types[1:]:
if previous * 2 == value:
result += "."
if previous == value:
if total >= previous:
total -= previous
else:
total -= previous / 2
total += previous / 2
value = previous / 2
previous = value
first_digit = str(int(total))
result = first_digit + result[1:]
return result
def addPlaceholder(self, duration=0, voice=1):
holder = NoteNode.Placeholder(duration=duration)
if self.getVoice(voice) is None:
self.addVoice(VoiceNode(), voice)
voice_obj = self.getVoice(voice)
children = voice_obj.GetChildrenIndexes()
if self.index >= len(children):
self.addNote(holder, voice)
else:
self.PositionChild(holder, self.index, voice=voice)
return None
def addDirection(self, item, voice=1):
wrappers = [Directions.Bracket.__name__]
if item.__class__.__name__ in wrappers and (
not hasattr(
item,
"type") or (
hasattr(
item,
"type") and item.type != "start")):
self.addWrapper(item)
return
if item.__class__.__name__ in wrappers and hasattr(
item,
"type") and item.type == "start":
if hasattr(item, "lineType"):
copy_obj = copy.deepcopy(item)
copy_obj.type = ""
item.lineType = ""
self.addWrapper(copy_obj)
if self.getVoice(voice) is None:
self.addVoice(VoiceNode(), voice)
direction_obj = DirectionNode()
direction_obj.SetItem(item)
voice_obj = self.getVoice(voice)
if self.index == 0:
finder = 0
else:
if item.__class__.__name__ == Directions.Pedal.__name__:
note = voice_obj.GetChild(self.index - 1)
if note is not None:
result = note.Find(DirectionNode, Directions.Pedal)
if result is not None:
self.index += 1
finder = self.index
note_obj = voice_obj.GetChild(finder)
if isinstance(
note_obj,
NoteNode.NoteNode) or isinstance(
note_obj,
NoteNode.Placeholder):
note_obj.AttachDirection(direction_obj)
else:
self.addPlaceholder()
if self.index >= len(voice_obj.children):
self.index = len(voice_obj.children) - 1
note_obj = voice_obj.GetChild(self.index)
if isinstance(note_obj, NoteNode.Placeholder):
note_obj.AttachDirection(direction_obj)
def addExpression(self, item, voice=1):
if self.getVoice(voice) is None:
self.addVoice(VoiceNode(), voice)
direction_obj = ExpressionNode()
direction_obj.SetItem(item)
voice_obj = self.getVoice(voice)
note_obj = voice_obj.GetChild(self.index)
if isinstance(
note_obj,
NoteNode.NoteNode) or isinstance(
note_obj,
NoteNode.Placeholder):
note_obj.AttachExpression(direction_obj)
else:
self.addPlaceholder()
note_obj = voice_obj.GetChild(self.index)
if isinstance(note_obj, NoteNode.Placeholder):
note_obj.AttachExpression(direction_obj)
def toLily(self):
'''
Method which converts the object instance, its attributes and children to a string of lilypond code
:return: str of lilypond code
'''
lilystring = ""
left_barline = self.GetBarline("left")
other_lefts = self.GetBarline("left-1")
if other_lefts is not None:
for left in other_lefts:
lilystring += left.toLily()
if left_barline is not None:
lilystring += left_barline.toLily()
lilystring += self.HandleAttributes()
voices = self.GetChildrenIndexes()
if not hasattr(self, "value"):
self.value = self.GetTotalValue()
if len(voices) > 1:
lilystring += "<<"
for voice in voices:
mid_barline = self.GetBarline("middle")
v_obj = self.getVoice(voice)
v_obj.autoBeam = self.autoBeam
if mid_barline is not None:
v_obj.mid_barline = mid_barline[0]
v_obj.total = self.value
if len(voices) > 1:
lilystring += " % voice " + str(voice) + "\n"
lilystring += "\\new Voice = \"" + \
helpers.NumbersToWords(voice) + "\"\n"
lilystring += "{\\voice" + \
helpers.NumbersToWords(voice).capitalize() + " "
lilystring += v_obj.toLily()
if len(voices) > 1:
lilystring += "}"
if len(voices) > 1:
lilystring += ">>"
lilystring += self.HandleClosingAttributes()
right_barline = self.GetBarline("right")
other_rights = self.GetBarline("right-1")
if other_rights is not None:
for right in other_rights:
lilystring += right.toLily()
if right_barline is not None:
lilystring += right_barline.toLily()
else:
lilystring += " | "
return lilystring
def CopyDirectionsAndExpressions(self, v_obj):
child = self.GetChildrenIndexes()[0]
voice = self.GetChild(child)
voice_notes = v_obj.GetChildrenIndexes()
for n in voice_notes:
note = v_obj.GetChild(n)
new_position = voice.GetChild(n)
if new_position is not None:
children_to_add = note.PopAllChildren()
for child in children_to_add:
if isinstance(child, NoteNode.NoteNode):
new_position.AttachNote(child)
if isinstance(child, ExpressionNode):
new_position.AttachExpression(child)
if isinstance(child, DirectionNode):
new_position.AttachDirection(child)
def RunVoiceChecks(self):
children = self.GetChildrenIndexes()
if len(children) > 1:
for child in children:
voice = self.GetChild(child)
total = voice.note_total
result = Search(NoteNode.NoteNode, voice, 1)
if result is None or total == 0:
voice = self.PopChild(child)
self.CopyDirectionsAndExpressions(voice)
voice.RunNoteChecks()
else:
voice = self.GetChild(1)
if voice is not None:
voice.RunNoteChecks()