MuseParse/classes/ObjectHierarchy/ItemClasses/Note.py
import math
from MuseParse.classes.ObjectHierarchy.ItemClasses import BaseClass, Mark, Ornaments
class Tie(BaseClass.Base):
"""
Class representing a tie.
Optional inputs:
type: either start or stop. Stop isn't particularly useful to lilypond but may be used in other output formats.
"""
def __init__(self, type):
BaseClass.Base.__init__(self)
if type is not None:
self.type = type
def toLily(self):
lily = ""
if hasattr(self, "type"):
if self.type == "start":
lily = "~"
return lily
class Notehead(BaseClass.Base):
"""
Class representing noteheads.
Optional inputs:
filled: whether or not the notehead is filled. bool.
type: type of notehead. str.
"""
def __init__(self, filled=False, type=""):
BaseClass.Base.__init__(self)
self.filled = filled
self.type = type
def toLily(self):
pre_note = "\n\\tweak #'style #'"
if self.type != "":
ignore = [
"slashed",
"back slashed",
"inverted triangle",
"arrow up",
"arrow down",
"normal"]
if self.type == "diamond":
val = "\\harmonic"
return [val]
if self.type == "x":
val = "\\xNote"
return [val, ""]
options = {
"diamond": "harmonic",
"x": "cross",
"circle-x": "xcircle"}
if self.type in options:
pre_note += options[self.type]
elif self.type not in ignore:
pre_note += self.type
if self.type in ignore:
pre_note = ""
val = ""
else:
pre_note = ""
return [pre_note + "\n", ""]
class Stem(BaseClass.Base):
"""
Class representing the note's stem.
optional input:
type: type of stem to show
"""
def __init__(self, type):
if type is not None:
self.type = type
BaseClass.Base.__init__(self)
def __str__(self):
return self.type
def toLily(self):
val = "\n\stem"
if not hasattr(self, "type"):
val += "Neutral"
else:
val += self.type[0].upper() + self.type[1:len(self.type)]
return val
class Pitch(BaseClass.Base):
"""
Class representing the pitch of the note
Optional inputs:
alter: how many semi tones to raise or lower the pitch. Generally either 1 or -1, float.
octave: number of the octave in which it resides in. int
accidental: accidental to show. Used where alter is not accurate enough, may indicate any range of accidentals such as
double sharps etc.
unpitched: bool representation of unpitchedness, aka a pitch which is like a clap or something rather than an actual note.
"""
def __init__(self, **kwargs):
if "alter" in kwargs:
self.alter = kwargs["alter"]
if "octave" in kwargs:
self.octave = int(kwargs["octave"])
if "step" in kwargs:
self.step = kwargs["step"]
if "accidental" in kwargs:
self.accidental = kwargs["accidental"]
if "unpitched" in kwargs:
self.unpitched = True
BaseClass.Base.__init__(self)
def __str__(self):
st = ""
alter = {1: "sharp", -
1: "flat", 0: "", 2: "double-sharp", -
2: "double-flat"}
if hasattr(self, "unpitched"):
st += "unpitched"
if hasattr(self, "step"):
st += self.step
if hasattr(self, "alter"):
st += alter[int(self.alter)]
if hasattr(self, "accidental"):
st += "(" + self.accidental + ")"
if hasattr(self, "octave"):
st += self.octave
return st
def toLily(self):
val = ""
if not hasattr(self, "step"):
val += "c"
else:
val += self.step.lower()
if hasattr(self, "alter"):
if self.alter == 1:
val += "is"
elif self.alter == -1:
val += "es"
if hasattr(self, "accidental"):
names = {
"three-quarters-sharp": "isih",
"three-quarters-flat": "eseh",
"quarter-sharp": "ih",
"quarter-flat": "eh",
"flat-flat": "eses",
"double-sharp": "isis"}
if self.accidental in names:
val += names[self.accidental]
if not hasattr(self, "octave"):
val += "'"
else:
oct = int(self.octave)
if oct > 3:
for i in range(oct - 3):
val += "'"
elif oct < 3:
counter = 3 - oct
while counter != 0:
val += ","
counter -= 1
return val
class Note(BaseClass.Base):
"""
Big class representing a note.
Optional inputs:
rest: bool as to whether this note is a rest or not.
dots: int - number of dots after the note, indicating extended length
pitch: a class representing the pitch of the note, see above
chord: bool indicating this note is part of a chord
type: string indicator of the length of note, like "quarter" or "half". Alternatively, duration may be given along with divisions
duration: length of note. Where musicXML is concerned, divisions should also be known, indicating how many divisions there are in
a quarter note.
"""
def __init__(self, **kwargs):
BaseClass.Base.__init__(self)
self.ties = []
self.beams = {}
if "rest" in kwargs:
self.rest = kwargs["rest"]
else:
self.rest = False
if "dots" in kwargs:
self.dots = kwargs["dots"]
else:
self.dots = 0
if "pitch" in kwargs:
self.pitch = kwargs["pitch"]
if "chord" in kwargs and kwargs["chord"] is not None:
self.chord = kwargs["chord"]
if "type" in kwargs and kwargs["type"] is not None:
self.SetType(kwargs["type"])
elif "duration" in kwargs:
self.duration = kwargs["duration"]
if "divisions" in kwargs and kwargs["divisions"] is not None:
self.divisions = float(kwargs["divisions"])
self.prenotation = []
'''any notation classes which come before the note is displayed - list'''
self.wrap_notation = []
'''any notation classes which have something to come before and something after the note is displayed - list'''
self.postnotation = []
'''notation to be shown after the note - list'''
self.closing_notation = []
'''notation to be shown after post notation - list'''
self.has_tremolo = False
def AddSlur(self, item):
'''
Very simple method which is used for adding slurs.
:param item:
:return:
'''
if not hasattr(self, "slurs"):
self.slurs = []
self.slurs.append(item)
def GetAllNotation(self):
return self.prenotation, self.wrap_notation, self.postnotation
def GetNotation(self, id, type):
'''
method which searches for notation from <type> list at position <id>
:param id: the number to look for - i.e if you're looking for the first one in wrap notation, id will be 0
:param type: post, pre or wrap
:return: the notation class searched for or none
'''
if type == "post":
if (id == -
1 and len(self.postnotation) > 0) or (id != -
1 and len(self.postnotation) > id):
return self.postnotation[id]
if type == "pre":
if (id == -
1 and len(self.prenotation) > 0) or (id != -
1 and len(self.postnotation) > id):
return self.prenotation[id]
if type == "wrap":
if (id == -
1 and len(self.wrap_notation) > 0) or (id != -
1 and len(self.postnotation) > id):
return self.wrap_notation[id]
def FlushNotation(self):
self.prenotation = []
self.wrap_notation = []
self.postnotation = []
self.closing_notation = []
def GetClosingNotationLilies(self):
'''
Converts notation in closing_notation into a lilypond string.
:return: str
'''
lstring = ""
for notation in self.closing_notation:
result = notation.toLily()
if type(result) == list:
result = "".join(result)
lstring += result
return lstring
def addNotation(self, obj):
'''
Method to add new notation. Use this rather than adding directly so new classes can be added automatically
without needing to know which list to add it to in the main code.
:param obj: the object to add
:return: None
'''
add = True
wrap_notation = [
Arpeggiate,
NonArpeggiate,
Slide,
Glissando,
Mark.Caesura,
Mark.BreathMark,
GraceNote]
# method to handle addition of notation: done here to avoid repetitive
# code in main parser
if isinstance(obj, Ornaments.Tremolo) or isinstance(obj, Tuplet):
if isinstance(obj, Ornaments.Tremolo):
options = {1: 2, 2: 4, 3: 8}
if hasattr(obj, "value"):
self.trem_length = options[obj.value]
if hasattr(obj, "type"):
if isinstance(obj, Ornaments.Tremolo) and obj.type != "single":
self.trem_length *= 2
if obj.type == "stop":
self.closing_notation.append(obj)
else:
self.prenotation.append(obj)
return
else:
self.prenotation.append(obj)
return
if type(obj) in wrap_notation:
if type(obj) == Slide and not hasattr(obj, "lineType"):
self.postnotation.append(obj)
return
else:
self.wrap_notation.append(obj)
return
if hasattr(obj, "type") and len(self.postnotation) > 0:
duplicate_check = [
True for thing in self.postnotation if hasattr(
thing,
"type") and thing.type == obj.type]
if len(duplicate_check) > 0:
add = False
if len(self.postnotation) == 0 or add:
self.postnotation.append(obj)
def SetType(self, vtype):
'''
Sets the type, i.e duration of the note. Types are given as keys inside options
:param vtype: str - see keys in options for full list
:return: None, side effects modifying the class
'''
self.val_type = vtype
options = {
"128th": 128,
"64th": 64,
"32nd": 32,
"16th": 16,
"eighth": 8,
"quarter": 4,
"half": 2,
"whole": 1,
"h": 8,
"long": "\\longa",
"breve": "\\breve"}
if vtype in options:
self.duration = options[self.val_type]
def CheckDivisions(self, measure_div):
'''
Method which is called from voice/measure to update the divisions for each note which are stored at
measure level, but needed at lilypond time to figure out lilypond notation
:param measure_div: number of divisions per note. Indicator of how big or small a quarter note is
:return: None, side effect
'''
if hasattr(self, "val_type"):
self.divisions = 1
else:
self.divisions = measure_div
def __str__(self):
if hasattr(self, "divisions") and hasattr(self, "duration"):
self.duration = self.duration / self.divisions
st = BaseClass.Base.__str__(self)
return st
def addDot(self):
self.dots += 1
def handlePreLilies(self):
'''
Fetches all notation to come before the note as a lilypond string
:return: str
'''
val = ""
if hasattr(self, "chord"):
if self.chord == "start":
val += "<"
if hasattr(self, "grace"):
val += self.grace.toLily() + " "
tuplet = self.Search(Tuplet, 1)
if tuplet is None and hasattr(self, "timeMod") and self.timeMod.first:
val += "\once \override TupletBracket.bracket-visibility = ##f\n"
val += "\omit TupletNumber\n"
val += "\\tuplet " + self.timeMod.toLily() + " {"
for item in self.prenotation:
lilystring = item.toLily()
if isinstance(item, Tuplet):
if hasattr(self, "timeMod"):
lilystring += " " + self.timeMod.toLily()
lilystring += " {"
if isinstance(item, Ornaments.Tremolo):
if not hasattr(self, "trem_length"):
self.trem_length = lilystring[1]
lilystring = lilystring[0]
val += lilystring
return val
def GetBeams(self):
if hasattr(self, "beams"):
return self.beams
def getLilyDuration(self):
"""
method to calculate duration of note in lilypond duration style
:return:
"""
value = ""
if not hasattr(self, "val_type"):
if hasattr(self, "duration") and self.duration is not None:
value = (self.duration / self.divisions)
value = (1 / value)
value *= 4
if value >= 1:
if math.ceil(value) == value:
if hasattr(self, "trem_length"):
value *= self.trem_length
value = int(value)
else:
rounded = math.ceil(value)
if hasattr(self, "trem_length"):
rounded *= self.trem_length
value = int(rounded)
else:
if value == 0.5:
value = "\\breve"
if value == 0.25:
value = "\longa"
else:
value = self.duration
if hasattr(self, "trem_length"):
value *= self.trem_length
if type(value) is not str:
value = int(value)
if value != "":
value = str(value)
return value
def addBeam(self, id, beam):
if not hasattr(self, "beams"):
self.beams = {}
result = [True for b in self.beams if self.beams[b].type == beam.type]
if len(result) < 1:
self.beams[id] = beam
def AddTie(self, type):
add = True
for tie in self.ties:
if tie.type == type:
add = False
break
if add:
self.ties.append(Tie(type))
def toLily(self):
val = ""
value = ""
if hasattr(self, "print") and not self.print:
val += "\n\\hideNotes\n"
val += self.handlePreLilies()
if hasattr(self, "pitch") and not self.rest:
val += self.pitch.toLily()
if self.rest:
if not hasattr(self, "MeasureRest") or not self.MeasureRest:
val += "r"
if hasattr(
self,
"duration") and (
not hasattr(
self,
"MeasureRest") or not self.MeasureRest):
if not hasattr(self, "chord"):
val += self.getLilyDuration()
for dot in range(self.dots):
val += "."
val += self.handlePostLilies()
value = self.LilyWrap(val)
if hasattr(self, "print"):
value += "\n\\unHideNotes"
if hasattr(self, "close_timemod") and self.close_timemod:
value += "}"
return value
def LilyWrap(self, value):
'''
Method to fetch lilypond representation of wrap_notation
:param value: current lilypond string to wrap
:return: updated lilypond string
'''
prefixes = ""
wrapped_notation_lilystrings = [
wrap.toLily() for wrap in self.wrap_notation]
if hasattr(self, "notehead"):
wrapped_notation_lilystrings.append(self.notehead.toLily())
prefixes += "".join([wrapper[0] +
" " for wrapper in wrapped_notation_lilystrings if wrapper is not None and len(wrapper) > 1])
prefixes_and_current = prefixes + value
postfixes = "".join(
[wrapper[-1] for wrapper in wrapped_notation_lilystrings if wrapper is not None and len(wrapper) > 0])
lilystring = prefixes_and_current + postfixes
return lilystring
def handlePostLilies(self):
val = ""
if hasattr(self, "chord") and self.chord == "stop":
val += ">"
val += self.getLilyDuration()
for dot in range(self.dots):
val += "."
if not hasattr(self, "chord") or self.chord == "stop":
if hasattr(
self,
"beams") and (
not hasattr(
self,
"autoBeam") or not self.autoBeam):
val += "".join([self.beams[beam].toLily()
for beam in self.beams])
if hasattr(self, "slurs"):
val += "".join([slur.toLily() for slur in self.slurs])
val += "".join([tie.toLily() for tie in self.ties])
val += "".join([value.toLily()
for value in self.postnotation if type(value.toLily()) is str])
val += "".join([value.toLily()[0] for value in self.postnotation if type(
value.toLily()) is list and len(value.toLily()) > 0])
return val
def Search(self, cls_type, list_id=-1):
'''
Method which looks for a particular class type in a particular list
:param cls_type: the type of object to find
:param list_id: the list it resides in
:return: the first object of cls_type, or None
'''
options = {
"pre": self.prenotation,
"post": self.postnotation,
"wrap": self.wrap_notation}
if list_id in options:
for item in options[list_id]:
if type(item) == cls_type:
return item
else:
for item in self.prenotation:
if type(item) == cls_type:
return item
for item in self.wrap_notation:
if item.__class__.__name__ == cls_type.__name__:
return item
else:
print(item.__class__.__name__, cls_type.__name__)
for item in self.postnotation:
if type(item) == cls_type:
return item
class Tuplet(BaseClass.Base):
"""
Tuplet class.
Optional inputs:
type: either start or stop. Represents that this is either the first or last tuplet in the group.
bracket: bool, indicating whether or not to bracket the tuplets.
"""
def __init__(self, **kwargs):
if "type" in kwargs:
if kwargs["type"] is not None:
self.type = kwargs["type"]
if "bracket" in kwargs:
if kwargs["bracket"] is not None:
self.bracket = kwargs["bracket"]
BaseClass.Base.__init__(self)
def toLily(self):
val = ""
if hasattr(self, "bracket"):
if self.bracket:
val += "\once \override TupletBracket.bracket-visibility = ##t\n"
else:
val += "\once \override TupletBracket.bracket-visibility = ##f\n"
val += "\\tuplet"
val = val
if hasattr(self, "type"):
if self.type == "stop":
val = "}"
return val
class GraceNote(BaseClass.Base):
"""
Gracenotes.
Optional inputs:
slash: bool - indicates whether or not the gracenote should be slashed
first: bool - indicates whether or not this is the first gracenote
attributes:
last: bool - indicates whether or not this is the last gracenote in a sequence of gracenotes.
"""
def __init__(self, **kwargs):
if "slash" in kwargs:
self.slash = kwargs["slash"]
if "first" in kwargs and kwargs["first"] is not None:
self.first = kwargs["first"]
BaseClass.Base.__init__(self)
def toLily(self):
val = "\grace"
ending = ""
if hasattr(self, "slash") and self.slash:
val = "\slashedGrace"
if hasattr(self, "first") and self.first:
val += " {"
else:
val = ""
if hasattr(self, "last") and self.last:
ending = " }"
if not hasattr(self, "first") or not self.first:
return ending
return [val, ending]
class TimeModifier(BaseClass.Base):
"""
Class representing a time mod: these sometimes appear in music xml where there are tuplets.
Optional inputs:
first: bool - indicates this is the first tuplet
normal: what the note would normally split into
actual: the modifier to actually use.
"""
def __init__(self, **kwargs):
BaseClass.Base.__init__(self)
self.first = False
if "first" in kwargs and kwargs["first"] is not None:
self.first = kwargs["first"]
if "normal" in kwargs:
self.normal = kwargs["normal"]
if "actual" in kwargs:
self.actual = kwargs["actual"]
def toLily(self):
val = ""
if hasattr(self, "actual"):
val += str(self.actual)
val += "/"
if hasattr(self, "normal"):
val += str(self.normal)
return val
class Arpeggiate(BaseClass.Base):
"""
Arpeggiate class
Optional inputs:
direction: direction the arrow head of the arpeggiate should put. Generally up or down I think
type: whether this is start/stop/none. None indicates it's somewhere in the middle.
"""
def __init__(self, **kwargs):
self.wrapped = True
BaseClass.Base.__init__(self)
if "direction" in kwargs:
self.direction = kwargs["direction"]
if "type" in kwargs:
self.type = kwargs["type"]
else:
self.type = "none"
def toLily(self):
var = "\\arpeggio"
if not hasattr(self, "direction") or self.direction is None:
var += "Normal"
else:
var += "Arrow" + self.direction.capitalize()
if self.type == "start":
return [var, ""]
if self.type == "stop":
return ["", "\\arpeggio"]
if self.type == "none":
return [""]
class Slide(BaseClass.Base):
"""
Optional Inputs:
type: the type of gliss, i.e start or stop
lineType: style of line to use
number: something that comes in from MusicXML but isn't actually used at min.
"""
def __init__(self, **kwargs):
self.wrapped = True
BaseClass.Base.__init__(self)
if "type" in kwargs:
if kwargs["type"] is not None:
self.type = kwargs["type"]
if "lineType" in kwargs:
if kwargs["lineType"] is not None:
self.lineType = kwargs["lineType"]
if "number" in kwargs:
if kwargs["number"] is not None:
self.number = kwargs["number"]
def toLily(self):
val = ""
gliss = "\glissando"
values = []
if hasattr(self, "lineType"):
if self.lineType == "wavy":
val += "\override Glissando.style = #'zigzag"
values.append(val)
if hasattr(self, "type"):
if self.type == "stop":
values = []
else:
values.append(gliss)
else:
values.append(gliss)
return values
class Glissando(Slide):
"""
A glissando - like a slide, but it really only comes in "wavy" type so lineType is completely ignored.
"""
def toLily(self):
self.lineType = "wavy"
vals = Slide.toLily(self)
return vals
class NonArpeggiate(Arpeggiate):
def __init__(self, **kwargs):
Arpeggiate.__init__(self)
if "type" in kwargs:
self.type = kwargs["type"]
def toLily(self):
if self.type == "start":
return ["\\arpeggioBracket", ""]
if self.type == "stop":
return ["", "\\arpeggio"]
if self.type == "none":
return [""]
class Beam(Stem):
"""
Class representing beam information. Normally this is automatic, but it comes in from MusicXML anyway
so may be useful at some stage.
# Optional input
- type - indicates whether this is a starting, continuing or ending beam.
"""
def toLily(self):
val = ""
if hasattr(self, "type"):
if self.type == "begin":
val = "["
elif self.type == "end":
val = "]"
return val