# ------------------------------------------------------------------------------
# Name: lily/objects.py
# Purpose: python objects representing lilypond
#
# Authors: Michael Scott Asato Cuthbert
# Jeremy Teitelbaum (Lilypond 2.24 adaptations)
#
# Copyright: Copyright © 2007-2025 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
'''
music21 translates to Lilypond format and if Lilypond is installed on the
local computer, can automatically generate .pdf, .png, and .svg versions
of musical files using Lilypond.
The Grammar for Lilypond comes from
http://lilypond.org/doc/v2.14/Documentation/notation/lilypond-grammar
'''
from __future__ import annotations
import typing as t
import unittest
from music21 import common
from music21 import exceptions21
from music21 import prebase
class LilyObjectsException(exceptions21.Music21Exception):
pass
[docs]
class LyObject(prebase.ProtoM21Object):
r'''
LyObject is the base class of all other Lily Objects
>>> lyo = lily.lilyObjects.LyObject()
>>> lyo.stringOutput()
''
'''
supportedClasses: list[object] = [] # ordered list of classes to support
m21toLy: dict[str, dict] = {}
defaultAttributes: dict[str, t.Any] = {}
backslash = '\\'
def __init__(self):
# self.context = context
self.lilyAttributes = {}
self._parent = None
self.thisIndent = 0
self.markupTop = None
self.lyricMarkupOrIdentifier = None
self.markupListOrIdentifier = None
self.markupTopOrIdentifier = None
# self.setLilyAttributes(inObject, context, **keywords)
def __setattr__(self, name, value):
if isinstance(value, LyObject):
value.setParent(self)
elif common.isIterable(value):
for v in value:
if isinstance(v, LyObject):
if v._parent is None:
v.setParent(self)
object.__setattr__(self, name, value)
[docs]
def getParent(self):
if self._parent is not None:
actualParent = common.unwrapWeakref(self._parent)
return actualParent
[docs]
def setParent(self, parentObject):
self._parent = common.wrapWeakref(parentObject)
[docs]
def ancestorList(self):
r'''
returns a list of all unwrapped parent objects for the current object
'''
ancestors = []
currentParent = self.getParent()
while currentParent is not None:
ancestors.append(currentParent)
currentParent = currentParent.getParent()
return ancestors
[docs]
def getAncestorByClass(self, classObj, getAncestorNumber=1):
currentIter = 1
for a in self.ancestorList():
if isinstance(a, classObj):
if currentIter == getAncestorNumber:
return a
else:
currentIter += 1
return None
@property
def newlineIndent(self):
# totalIndents = self.thisIndent
ancestors = self.ancestorList()
# for ancestor in ancestors:
# totalIndents += ancestor.thisIndent
totalIndents = len(ancestors)
indentSpaces = ' ' * totalIndents
return '\n' + indentSpaces
[docs]
def setAttributes(self, m21Object):
r'''
Returns a dictionary and sets self.lilyAttributes to that dictionary, for a m21Object
of class classLookup using the mapping of self.m21toLy[classLookup]
>>> class Mock(base.Music21Object):
... pass
>>> m = Mock()
>>> m.mockAttribute = 32
>>> m.mockAttribute2 = None
>>> lm = lily.lilyObjects.LyMock()
LyMock (our test class) defines mappings for two classes:
to LyMock.lilyAttributes:
>>> print(lm.supportedClasses)
['Mock', 'Mocker']
Thus, we can get attributes from the Mock class (see `setAttributesFromClassObject`):
>>> lilyAttributes = lm.setAttributes(m)
>>> for x in sorted(lilyAttributes.keys()):
... print(f'{x}: {lilyAttributes[x]}')
mock-attribute: 32
mock-attribute-2: None
>>> lilyAttributes is lm.lilyAttributes
True
'''
attrs = None
foundClass = False
for tryClass in self.supportedClasses:
if tryClass in m21Object.classes or tryClass == '*':
attrs = self.setAttributesFromClassObject(tryClass, m21Object)
foundClass = True
break
if not foundClass: # pragma: no cover
raise LilyObjectsException(
'Could not support setting attributes from '
f'{m21Object}: supported classes: {self.supportedClasses}')
return attrs
[docs]
def setAttributesFromClassObject(self, classLookup, m21Object):
r'''
Returns a dictionary and sets self.lilyAttributes to that dictionary, for a m21Object
of class classLookup using the mapping of self.m21toLy[classLookup]
>>> class Mock(base.Music21Object): pass
>>> m = Mock()
>>> lm = lily.lilyObjects.LyMock()
LyMock (our test class) defines certain mappings from the m21 Mock class
to LyMock.lilyAttributes:
>>> for x in sorted(lm.m21toLy['Mock'].keys()):
... print(f"{x}: {lm.m21toLy['Mock'][x]}")
mockAttribute: mock-attribute
mockAttribute2: mock-attribute-2
Some of these attributes have defaults:
>>> for x in sorted(lm.defaultAttributes.keys()):
... print(f'{x}: {lm.defaultAttributes[x]}')
mockAttribute2: 7
>>> m.mockAttribute = 'hello'
>>> lilyAttributes = lm.setAttributesFromClassObject('Mock', m)
>>> for x in sorted(lilyAttributes.keys()):
... print(f'{x}: {lilyAttributes[x]}')
mock-attribute: hello
mock-attribute-2: 7
>>> lilyAttributes is lm.lilyAttributes
True
'''
if classLookup not in self.m21toLy: # pragma: no cover
raise LilyObjectsException(
'Could not support setting attributes from '
f'{m21Object} error in self.m21toLy,'
' missing class definitions and no "*"'
)
classDict = self.m21toLy[classLookup]
for m21Attribute in classDict:
try:
value = getattr(m21Object, m21Attribute)
except AttributeError:
if m21Attribute in self.defaultAttributes:
value = self.defaultAttributes[m21Attribute]
else:
value = None
lyAttribute = classDict[m21Attribute]
# print(m21Attribute, lyAttribute, value)
self.lilyAttributes[lyAttribute] = value
return self.lilyAttributes
def _reprInternal(self) -> str:
msg = str(self)
msg.replace('\n', ' ')
if len(msg) >= 13:
msg = msg[:10] + '...'
return msg
def __str__(self):
so = self.stringOutput()
so = so.replace('\n\n', '\n')
return so
[docs]
def stringOutput(self):
return ''
[docs]
def getFirstNonNoneAttribute(self, attributeList):
for a in attributeList:
if getattr(self, a) is not None:
return getattr(self, a)
return None
[docs]
def newlineSeparateStringOutputIfNotNone(self, contents):
c = ''
for n in contents:
if n is None:
continue
c += str(n) + self.newlineIndent
return c
[docs]
def encloseCurly(self, arg):
if isinstance(arg, list):
strArg = self.newlineIndent.join(arg)
return ''.join([' { ', self.newlineIndent, strArg, self.newlineIndent,
' } ', self.newlineIndent])
elif arg is not None:
return ''.join([' { ', self.newlineIndent, str(arg), self.newlineIndent,
' } ', self.newlineIndent])
else:
return ' { } '
[docs]
def quoteString(self, stringIn):
r'''
returns a string that is quoted with
internal quotation marks backslash'd out
and an extra space at the end.
>>> m = lily.lilyObjects.LyObject()
>>> print(m.quoteString(r'Hello "there"!'))
"Hello \"there\"!"
'''
stringNew = stringIn.replace('"', r'\"')
return '"' + stringNew + '" '
[docs]
class LyMock(LyObject):
r'''
A test object for trying various music21 to Lily conversions
'''
supportedClasses = ['Mock', 'Mocker']
m21toLy = {'Mock': {'mockAttribute': 'mock-attribute',
'mockAttribute2': 'mock-attribute-2',
},
'Mocker': {'mockerAttribute': 'mock-attribute',
'greg': 'mock-attribute-2', },
}
defaultAttributes = {'mockAttribute2': 7,
}
# ----------Grammar------------------#
[docs]
class LyLilypondTop(LyObject):
r'''
corresponds to the highest level lilypond object in Appendix C:
::
`lilypond: /* empty */
| lilypond toplevel_expression
| lilypond assignment
| lilypond error
| lilypond "\invalid"`
error and \invalid are not defined by music21
'''
canContain = [None, 'TopLevelExpression', 'Assignment']
def __init__(self, contents=None):
if contents is None:
contents = []
super().__init__()
self.contents = contents
[docs]
def stringOutput(self):
return self.newlineSeparateStringOutputIfNotNone(self.contents)
[docs]
class LyTopLevelExpression(LyObject):
r'''
can contain one of:
lilypondHeader
bookBlock
bookPartBlock
scoreBlock
compositeMusic
fullMarkup
fullMarkupList
outputDef
>>> bookBlock = lily.lilyObjects.LyBookBlock()
>>> lyTopLevel = lily.lilyObjects.LyTopLevelExpression(bookBlock=bookBlock)
>>> str(lyTopLevel)
'\\book { } '
'''
def __init__(self, lilypondHeader=None, bookBlock=None,
bookPartBlock=None, scoreBlock=None, compositeMusic=None,
fullMarkup=None, fullMarkupList=None, outputDef=None
):
super().__init__()
self.lilypondHeader = lilypondHeader
self.bookBlock = bookBlock
self.bookPartBlock = bookPartBlock
self.scoreBlock = scoreBlock
self.compositeMusic = compositeMusic
self.fullMarkup = fullMarkup
self.fullMarkupList = fullMarkupList
self.outputDef = outputDef
[docs]
def stringOutput(self):
outputObject = self.getFirstNonNoneAttribute([
'lilypondHeader', 'bookBlock', 'bookPartBlock', 'scoreBlock',
'compositeMusic', 'fullMarkup', 'fullMarkupList', 'outputDef'])
if outputObject is None:
raise LilyObjectsException('Need an outputObject to report') # pragma: no cover
return outputObject.stringOutput()
[docs]
class LyEmbeddedScm(LyObject):
r'''
represents Scheme embedded in Lilypond code.
Can be either an SCM_TOKEN (Scheme Token) or SCM_IDENTIFIER String stored in self.content
Note that if any LyEmbeddedScm is found in an output then the output SHOULD be marked as unsafe.
But a lot of standard lilypond functions are actually embedded scheme.
For instance, \clef, which
as http://lilypond.org/doc/v2.12/input/lsr/lilypond-snippets/Pitches#Tweaking-clef-properties
shows is a macro to run a lot of \set commands.
>>> lyScheme = lily.lilyObjects.LyEmbeddedScm('##t')
>>> str(lyScheme)
'##t'
'''
def __init__(self, content=None):
super().__init__()
self.content = content
[docs]
def stringOutput(self):
return self.content
[docs]
class LyLilypondHeaderBody(LyObject):
def __init__(self, assignments=None):
if assignments is None:
assignments = []
super().__init__()
self.assignments = assignments
[docs]
def stringOutput(self):
return self.newlineSeparateStringOutputIfNotNone(self.assignments)
[docs]
class LyAssignmentId(LyObject):
'''
>>> lyAssignmentId = lily.lilyObjects.LyAssignmentId('title', isLyricString=False)
>>> str(lyAssignmentId)
'title'
'''
def __init__(self, content=None, isLyricString=False):
super().__init__()
self.content = content
self.isLyricString = isLyricString
[docs]
def stringOutput(self):
return self.content
[docs]
class LyAssignment(LyObject):
r'''
one of three forms of assignment:
assignment_id '=' identifier_init
assignment_id property_path '=' identifier_init
embedded_scm
if self.embeddedScm is not None, uses type 3
if self.propertyPath is not None, uses type 2
else uses type 1 or raises an exception.
>>> lyIdInit = lily.lilyObjects.LyIdentifierInit(string='hi')
>>> lya = lily.lilyObjects.LyAssignment(assignmentId='title', identifierInit=lyIdInit)
>>> print(lya)
title = "hi"
Note that you could also pass assignmentId a LyAssignmentId object,
but that's overkill for a lot of things.
'''
def __init__(self, assignmentId=None, identifierInit=None,
propertyPath=None, embeddedScm=None):
super().__init__()
self.assignmentId = assignmentId
self.identifierInit = identifierInit
self.propertyPath = propertyPath
self.embeddedScm = embeddedScm
[docs]
def stringOutput(self):
if self.embeddedScm is not None:
return self.embeddedScm.stringOutput()
elif self.propertyPath is not None:
if self.assignmentId is None or self.identifierInit is None: # pragma: no cover
raise LilyObjectsException('need an assignmentId or identifierInit')
return ''.join([str(self.assignmentId), ' ',
self.propertyPath.stringOutput(), ' = ',
self.identifierInit.stringOutput(), ' '])
else:
if self.assignmentId is None or self.identifierInit is None: # pragma: no cover
raise LilyObjectsException('need an assignmentId or identifierInit')
return ' '.join([str(self.assignmentId), '=',
self.identifierInit.stringOutput(), ' '])
[docs]
class LyIdentifierInit(LyObject):
r'''
>>> lyIdInit = lily.lilyObjects.LyIdentifierInit(string='hello')
>>> print(lyIdInit)
"hello"
'''
def __init__(self,
scoreBlock=None,
bookBlock=None,
bookPartBlock=None,
outputDef=None,
contextDefSpecBlock=None,
music=None, postEvent=None, numberExpression=None,
string=None, embeddedScm=None, fullMarkup=None, fullMarkupList=None,
digit=None, contextModification=None):
super().__init__()
self.scoreBlock = scoreBlock
self.bookBlock = bookBlock
self.bookPartBlock = bookPartBlock
self.outputDef = outputDef
self.contextDefSpecBlock = contextDefSpecBlock
self.music = music
self.postEvent = postEvent
self.numberExpression = numberExpression
self.string = string
self.embeddedScm = embeddedScm
self.fullMarkup = fullMarkup
self.fullMarkupList = fullMarkupList
self.digit = digit
self.contextModification = contextModification
[docs]
def stringOutput(self):
outputObject = self.getFirstNonNoneAttribute([
'scoreBlock', 'bookBlock', 'bookPartBlock', 'outputDef',
'contextDefSpecBlock', 'music', 'postEvent', 'numberExpression',
'string', 'embeddedScm', 'fullMarkup', 'fullMarkupList',
'digit', 'contextModification'])
if outputObject is None:
raise LilyObjectsException('need an outputObject') # pragma: no cover
if outputObject is self.digit: # better test for digit
return str(outputObject)
elif outputObject is self.string:
return self.quoteString(outputObject)
else:
return outputObject.stringOutput()
[docs]
class LyContextDefSpecBlock(LyObject):
def __init__(self, contextDefSpecBody=None):
super().__init__()
self.contextDefSpecBody = contextDefSpecBody
[docs]
def stringOutput(self):
return self.backslash + 'context ' + self.encloseCurly(self.contextDefSpecBody)
[docs]
class LyContextDefSpecBody(LyObject):
r'''
None or one of four forms:
CONTEXT_DEF_IDENTIFIER
context_def_spec_body "\grobdescriptions" embedded_scm
context_def_spec_body context_mod
context_def_spec_body context_modification
>>> lyContextBody = lily.lilyObjects.LyContextDefSpecBody(contextDefIdentifier='cdi')
>>> lyContextBody.stringOutput()
'cdi'
>>> embedScm = lily.lilyObjects.LyEmbeddedScm('#t')
>>> lyContextBody = lily.lilyObjects.LyContextDefSpecBody(
... contextDefSpecBody='body', embeddedScm=embedScm)
>>> lyContextBody.stringOutput()
'body \\grobdescriptions #t'
'''
def __init__(self, contextDefIdentifier=None, contextDefSpecBody=None,
embeddedScm=None, contextMod=None, contextModification=None):
super().__init__()
self.contextDefIdentifier = contextDefIdentifier
self.contextDefSpecBody = contextDefSpecBody
self.embeddedScm = embeddedScm
self.contextMod = contextMod
self.contextModification = contextModification
[docs]
def stringOutput(self):
if self.contextDefIdentifier is not None:
return self.contextDefIdentifier
elif self.embeddedScm is not None:
out = ''
if self.contextDefSpecBody is not None:
out = ''.join([self.contextDefSpecBody, ' ', self.backslash,
'grobdescriptions', ' '])
out += self.embeddedScm.stringOutput()
return out
elif self.contextMod is not None:
if self.contextDefSpecBody is not None:
return self.contextDefSpecBody + ' ' + self.contextMod.stringOutput()
else:
return self.contextMod.stringOutput()
elif self.contextModification is not None:
if self.contextDefSpecBody is not None:
return ' '.join([self.contextDefSpecBody,
self.contextModification.stringOutput()])
else:
return self.contextModification.stringOutput()
else:
return None
[docs]
class LyBookBlock(LyObject):
def __init__(self, bookBody=None):
super().__init__()
self.bookBody = bookBody
[docs]
def stringOutput(self):
return self.backslash + 'book' + ' ' + self.encloseCurly(self.bookBody)
[docs]
class LyBookBody(LyObject):
r'''
Contains None, bookIdentifier (string?) or one or more of the following:
paperBlock
bookPartBlock
scoreBlock
compositeMusic
fullMarkup
fullMarkupList
lilypondHeader
error
>>> lyBookBody = lily.lilyObjects.LyBookBody(bookIdentifier='bookId')
>>> lyBookBody.stringOutput()
'bookId'
>>> lyBookBody = lily.lilyObjects.LyBookBody()
>>> lyBookBody.stringOutput() is None
True
>>> lyBookBody = lily.lilyObjects.LyBookBody(contents=['a', 'b', 'c'])
>>> print(lyBookBody.stringOutput())
a
b
c
'''
def __init__(self, contents=None, bookIdentifier=None):
if contents is None:
contents = []
super().__init__()
self.contents = contents
self.bookIdentifier = bookIdentifier
[docs]
def stringOutput(self):
if self.bookIdentifier is not None:
return self.bookIdentifier
elif not self.contents:
return None
else:
return self.newlineSeparateStringOutputIfNotNone(self.contents)
[docs]
class LyBookpartBlock(LyObject):
r'''
>>> lbb = lily.lilyObjects.LyBookpartBlock()
>>> lbb.stringOutput()
'\\bookpart { \n\n } \n'
'''
def __init__(self, bookpartBody=None):
super().__init__()
self.bookpartBody = bookpartBody
[docs]
def stringOutput(self):
if self.bookpartBody is None:
return self.backslash + 'bookpart ' + self.encloseCurly('')
else:
return self.backslash + 'bookpart ' + self.encloseCurly(
self.bookpartBody.stringOutput())
[docs]
class LyBookpartBody(LyObject):
r'''
Contains None, bookIdentifier (string?) or one or more of the following:
paperBlock
scoreBlock
compositeMusic
fullMarkup
fullMarkupList
lilypondHeader
error
>>> lyBookpartBody = lily.lilyObjects.LyBookpartBody(bookIdentifier='bookId')
>>> lyBookpartBody.stringOutput()
'bookId'
>>> lyBookpartBody = lily.lilyObjects.LyBookpartBody()
>>> lyBookpartBody.stringOutput() is None
True
>>> lyBookpartBody = lily.lilyObjects.LyBookpartBody(contents=['a', 'b', 'c'])
>>> print(lyBookpartBody.stringOutput())
a
b
c
'''
def __init__(self, contents=None, bookIdentifier=None):
if contents is None:
contents = []
super().__init__()
self.contents = contents
self.bookIdentifier = bookIdentifier
[docs]
def stringOutput(self):
if self.bookIdentifier is not None:
return self.bookIdentifier
elif not self.contents:
return None
else:
return self.newlineSeparateStringOutputIfNotNone(self.contents)
[docs]
class LyScoreBlock(LyObject):
r'''
represents the container for a score ( \score { ... } )
with all the real stuff being in self.scoreBody
>>> lyScoreBlock = lily.lilyObjects.LyScoreBlock(scoreBody='hello')
>>> print(lyScoreBlock)
\score { hello }
'''
def __init__(self, scoreBody=None):
super().__init__()
self.scoreBody = scoreBody
[docs]
def stringOutput(self):
if self.scoreBody is None:
raise LilyObjectsException('scoreBody object cannot be empty!') # pragma: no cover
return self.backslash + 'score ' + self.encloseCurly(self.scoreBody)
[docs]
class LyScoreBody(LyObject):
r'''
represents the contents of a \score { contents }
block
can take one of the following attributes:
music, scoreIdentifier, scoreBody, lilypondHeader, outputDef, error
>>> lsb = lily.lilyObjects.LyScoreBody(scoreIdentifier='score')
>>> str(lsb)
'score'
'''
def __init__(self, music=None, scoreIdentifier=None, scoreBody=None, lilypondHeader=None,
outputDef=None, error=None):
super().__init__()
self.music = music
self.scoreIdentifier = scoreIdentifier
self.scoreBody = scoreBody
self.lilypondHeader = lilypondHeader
self.outputDef = outputDef
self.error = error
[docs]
def stringOutput(self):
if self.music is not None:
return self.music.stringOutput()
elif self.scoreIdentifier is not None:
return self.scoreIdentifier
elif self.scoreBody is None:
raise LilyObjectsException(
'scoreBody cannot be None if music and scoreIdentifier are None')
elif self.lilypondHeader is not None:
return self.scoreBody.stringOutput() + ' ' + self.lilypondHeader.stringOutput()
elif self.outputDef is not None:
return self.scoreBody.stringOutput() + ' ' + self.outputDef.stringOutput()
elif self.error is not None:
return self.scoreBody.stringOutput() + ' ' + self.error.stringOutput()
else:
raise LilyObjectsException(
'one of music, scoreIdentifier, lilypondHeader, outputDef, or error '
+ 'must not be None')
[docs]
class LyPaperBlock(LyObject):
def __init__(self, outputDef=None):
super().__init__()
self.outputDef = outputDef
[docs]
def stringOutput(self):
if self.outputDef is None: # legal??
return None
else:
return self.outputDef.stringOutput()
[docs]
class LyLayout(LyObject):
[docs]
def stringOutput(self):
theseStrings = [self.backslash + 'layout {',
' ' + self.backslash + 'context {',
' ' + self.backslash + 'RemoveEmptyStaves',
' ' + self.backslash + 'override VerticalAxisGroup.remove-first = ##t',
' ' + '}', '}']
return self.newlineSeparateStringOutputIfNotNone(theseStrings)
[docs]
class LyOutputDef(LyObject):
r'''
This is an ugly grammar, since it does not close the curly bracket.
'''
def __init__(self, outputDefBody=None):
super().__init__()
self.outputDefBody = outputDefBody
[docs]
def stringOutput(self):
if self.outputDefBody is None:
raise LilyObjectsException('Need outputDefBody to be set') # pragma: no cover
return self.outputDefBody.stringOutput() + '}'
[docs]
class LyOutputDefHead(LyObject):
r'''
defType can be paper, midi, or layout.
>>> lyODH = lily.lilyObjects.LyOutputDefHead()
>>> lyODH.defType = 'midi'
>>> print(lyODH.stringOutput())
\midi
According to Appendix C, is the same as LyOutputDefHeadWithModeSwitch
'''
def __init__(self, defType=None):
super().__init__()
self.defType = defType
[docs]
def stringOutput(self):
if self.defType not in ('paper', 'midi', 'layout'): # pragma: no cover
raise LilyObjectsException("self.defType must be one of 'paper', 'midi', or 'layout'")
return self.backslash + self.defType
[docs]
class LyOutputDefBody(LyObject):
r'''
output_def_body: output_def_head_with_mode_switch '{'
| output_def_head_with_mode_switch
'{'
OUTPUT_DEF_IDENTIFIER
| output_def_body assignment
| output_def_body context_def_spec_block
| output_def_body error
'''
def __init__(self, outputDefHead=None, outputDefIdentifier=None, outputDefBody=None,
assignment=None, contextDefSpecBlock=None, error=None):
super().__init__()
self.outputDefHead = outputDefHead
self.outputDefIdentifier = outputDefIdentifier
self.outputDefBody = outputDefBody
self.assignment = assignment
self.contextDefSpecBlock = contextDefSpecBlock
self.error = error
[docs]
def stringOutput(self):
if self.outputDefHead is not None:
out = str(self.outputDefHead) + ' { '
if self.outputDefIdentifier is not None:
return out + str(self.outputDefIdentifier)
else:
return out
elif self.outputDefBody is not None: # pragma: no cover
raise LilyObjectsException('Need embedded outputDefBody if outputDefIdentifier '
+ 'or outputDefHead are not defined')
elif self.assignment is not None:
return self.outputDefBody.stringOutput() + ' ' + self.assignment.stringOutput()
elif self.contextDefSpecBlock is not None:
return self.outputDefBody.stringOutput() + ' ' + self.contextDefSpecBlock.stringOutput()
elif self.error is not None:
return self.outputDefBody.stringOutput() + ' ' + self.error.stringOutput()
else: # pragma: no cover
raise LilyObjectsException('Need to define at least one of assignment, '
+ 'contextDefSpecBlock, or error if outputDefHead is None')
[docs]
class LyTempoEvent(LyObject):
r'''
tempo_event: "\tempo" steno_duration '=' tempo_range
| "\tempo" scalar steno_duration '=' tempo_range
| "\tempo" scalar
>>> lte = lily.lilyObjects.LyTempoEvent(scalar='40')
>>> str(lte)
'\\tempo 40'
More complex:
>>> steno = lily.lilyObjects.LyStenoDuration('quarter')
>>> tempoRange = lily.lilyObjects.LyTempoRange(70, 100)
>>> lte = lily.lilyObjects.LyTempoEvent(tempoRange=tempoRange, stenoDuration=steno)
>>> str(lte)
'\\tempo quarter = 70~100 '
>>> lte.scalar = 85
>>> str(lte)
'\\tempo 85 quarter = 70~100 '
'''
def __init__(self, tempoRange=None, stenoDuration=None, scalar=None):
super().__init__()
self.tempoRange = tempoRange
self.stenoDuration = stenoDuration
self.scalar = scalar
[docs]
def stringOutput(self):
base = self.backslash + 'tempo'
if self.tempoRange is not None:
if self.stenoDuration is None: # pragma: no cover
raise LilyObjectsException('If tempoRange is defined then need a stenoDuration')
if self.scalar is not None:
return ' '.join([base,
str(self.scalar),
self.stenoDuration.stringOutput(), '=',
self.tempoRange.stringOutput()])
else:
return ' '.join([base, self.stenoDuration.stringOutput(),
'=', self.tempoRange.stringOutput()])
elif self.scalar is None: # pragma: no cover
raise LilyObjectsException('If tempoRange is not defined then need scalar')
return base + ' ' + str(self.scalar)
[docs]
class LyMusicList(LyObject):
r'''
can take any number of LyMusic, LyEmbeddedScm, or LyError objects
'''
def __init__(self, contents=None):
super().__init__()
if contents is None:
contents = []
self.contents = contents
[docs]
def stringOutput(self):
return self.newlineSeparateStringOutputIfNotNone(self.contents)
[docs]
class LyMusic(LyObject):
def __init__(self, simpleMusic=None, compositeMusic=None):
super().__init__()
self.simpleMusic = simpleMusic
self.compositeMusic = compositeMusic
[docs]
def stringOutput(self):
if self.simpleMusic is not None:
return self.simpleMusic.stringOutput()
elif self.compositeMusic is not None:
return self.compositeMusic.stringOutput()
else: # pragma: no cover
raise LilyObjectsException('Need to define one of simpleMusic or compositeMusic')
[docs]
class LyAlternativeMusic(LyObject):
def __init__(self, musicList=None):
super().__init__()
self.musicList = musicList
[docs]
def stringOutput(self):
if self.musicList is None:
return ''
else:
return self.backslash + 'alternative' + self.encloseCurly(self.musicList)
[docs]
class LyRepeatedMusic(LyObject):
def __init__(self, simpleString=None, unsignedNumber=None, music=None, alternativeMusic=None):
super().__init__()
self.simpleString = simpleString
self.unsignedNumber = unsignedNumber
self.music = music
self.alternativeMusic = alternativeMusic
[docs]
def stringOutput(self):
out = (self.backslash
+ 'repeat '
+ self.simpleString.stringOutput()
+ self.unsignedNumber.stringOutput()
+ self.music.stringOutput())
if self.alternativeMusic is None:
return out
else:
return out + ' ' + self.alternativeMusic.stringOutput()
[docs]
class LySequentialMusic(LyObject):
r'''
represents sequential music.
Can be explicitly tagged with "\sequential" if displayTag is True
'''
def __init__(self, musicList=None, displayTag=False, beforeMatter=None):
super().__init__()
self.musicList = musicList
self.displayTag = displayTag
self.beforeMatter = beforeMatter
[docs]
def stringOutput(self):
if self.musicList is not None:
musicListSO = self.musicList.stringOutput()
else:
musicListSO = ''
tag = ''
if self.displayTag is True:
tag = self.backslash + 'sequential '
if self.beforeMatter == 'startStaff':
beforeMatter = self.backslash + 'startStaff '
else:
beforeMatter = ''
return tag + '{ ' + beforeMatter + musicListSO + ' } ' + self.newlineIndent
# + self.encloseCurly(musicListSO)
[docs]
class LyOssiaMusic(LyObject):
r'''
represents ossia music.
Can be tagged with \startStaff and \stopStaff if startstop is True
'''
def __init__(self, musicList=None, startstop=True):
super().__init__()
self.musicList = musicList
self.startstop = startstop
[docs]
def stringOutput(self):
if self.startstop is True:
start = self.backslash + 'startStaff '
stop = self.backslash + 'stopStaff'
else:
start, stop = '', ''
if self.musicList is not None:
musicListSO = self.musicList.stringOutput()
else:
musicListSO = ''
return '{' + start + musicListSO + ' ' + stop + '}' + self.newlineIndent
[docs]
class LySimultaneousMusic(LyObject):
r'''
represents simultaneous music.
Can be explicitly tagged with '\simultaneous' if displayTag is True
otherwise encloses in double angle brackets
'''
def __init__(self, musicList=None, displayTag=False):
super().__init__()
self.musicList = musicList
self.displayTag = displayTag
[docs]
def stringOutput(self):
if self.musicList is not None:
musicListSO = self.musicList.stringOutput()
else:
musicListSO = ''
# tag = ''
if self.displayTag is True:
return self.backslash + 'simultaneous ' + self.encloseCurly(musicListSO)
else:
return ''.join([self.newlineIndent, '<< ', musicListSO, ' >>', self.newlineIndent])
[docs]
class LySimpleMusic(LyObject):
def __init__(self, eventChord=None, musicIdentifier=None,
musicPropertyDef=None, contextChange=None):
super().__init__()
self.eventChord = eventChord
self.musicIdentifier = musicIdentifier
self.musicPropertyDef = musicPropertyDef
self.contextChange = contextChange
[docs]
def stringOutput(self):
outputObject = self.getFirstNonNoneAttribute(['eventChord', 'musicIdentifier',
'musicPropertyDef', 'contextChange'])
if outputObject is None:
raise LilyObjectsException('need one attribute set') # pragma: no cover
return outputObject.stringOutput()
[docs]
class LyContextModification(LyObject):
r'''
represents both context_modification and optional_context_mod
but not context_mod!!!!!
'''
def __init__(self, contextModList=None, contextModIdentifier=None, displayWith=True):
super().__init__()
self.contextModList = contextModList
self.contextModIdentifier = contextModIdentifier # String?
self.displayWith = displayWith # optional, but not supported without so far
[docs]
def stringOutput(self):
if self.contextModList is not None:
return self.backslash + 'with ' + self.encloseCurly(self.contextModList)
elif self.contextModIdentifier is not None:
return self.backslash + 'with ' + self.contextModIdentifier
else:
return ''
[docs]
class LyContextModList(LyObject):
r'''
contains zero or more LyContextMod objects and an optional contextModIdentifier
'''
def __init__(self, contents=None, contextModIdentifier=None):
if contents is None:
contents = []
super().__init__()
self.contents = contents
self.contextModIdentifier = contextModIdentifier # STRING
[docs]
def stringOutput(self):
output = self.newlineSeparateStringOutputIfNotNone(self.contents)
if self.contextModIdentifier is not None:
return output + ' ' + self.contextModIdentifier
else:
return output
[docs]
class LyCompositeMusic(LyObject):
r'''
one of LyPrefixCompositeMusic or LyGroupedMusicList stored in self.contents
'''
def __init__(self, prefixCompositeMusic=None, groupedMusicList=None, newLyrics=None):
super().__init__()
self.prefixCompositeMusic = prefixCompositeMusic
self.groupedMusicList = groupedMusicList
self.newLyrics = newLyrics
@property
def contents(self):
if self.prefixCompositeMusic is not None:
return self.prefixCompositeMusic
else:
return self.groupedMusicList
[docs]
def stringOutput(self):
if self.newLyrics is not None:
newLyrics = self.newLyrics
else:
newLyrics = ''
if self.prefixCompositeMusic is not None:
return str(self.prefixCompositeMusic) + '\n' + str(newLyrics)
elif self.groupedMusicList is not None:
return str(self.groupedMusicList) + '\n' + str(newLyrics)
else:
raise LilyObjectsException(
'Need to define either prefixCompositeMusic or groupedMusicList')
[docs]
class LyGroupedMusicList(LyObject):
r'''
one of LySimultaneousMusic or LySequentialMusic
'''
def __init__(self, simultaneousMusic=None, sequentialMusic=None):
super().__init__()
self.simultaneousMusic = simultaneousMusic
self.sequentialMusic = sequentialMusic
[docs]
def stringOutput(self):
if self.simultaneousMusic is not None:
return str(self.simultaneousMusic)
elif self.sequentialMusic is not None:
return str(self.sequentialMusic)
else: # pragma: no cover
raise LilyObjectsException(
'Need to define either simultaneousMusic or sequentialMusic')
[docs]
class LySchemeFunction(LyObject):
r'''
Unsupported for now, represents all of::
function_scm_argument: embedded_scm
116 | simple_string
117 function_arglist_music_last: EXPECT_MUSIC function_arglist music
118 function_arglist_nonmusic_last: EXPECT_MARKUP
function_arglist
full_markup
119 | EXPECT_MARKUP
function_arglist
simple_string
120 | EXPECT_SCM
function_arglist
function_scm_argument
121 function_arglist_nonmusic: EXPECT_NO_MORE_ARGS
122 | EXPECT_MARKUP
function_arglist_nonmusic
full_markup
123 | EXPECT_MARKUP
function_arglist_nonmusic
simple_string
124 | EXPECT_SCM
function_arglist_nonmusic
function_scm_argument
125 function_arglist: EXPECT_NO_MORE_ARGS
126 | function_arglist_music_last
127 | function_arglist_nonmusic_last
128 generic_prefix_music_scm: MUSIC_FUNCTION function_arglist
We have usually been using LyEmbeddedScm for this
'''
def __init__(self, content=None):
super().__init__()
self.content = content
[docs]
def stringOutput(self):
if self.content is None:
return None
else:
return str(self.content)
[docs]
class LyOptionalId(LyObject):
r'''
an optional id setting
'''
def __init__(self, content=None):
super().__init__()
self.content = content
[docs]
def stringOutput(self):
if self.content is None:
return None
else:
return ' = ' + self.content
[docs]
class LyPrefixCompositeMusic(LyObject):
r'''
type must be specified. Should be one of:
scheme, context, new, times, repeated, transpose,
modeChanging, modeChangingWith, relative,
rhythmed
prefix_composite_music: generic_prefix_music_scm
| "\context"
simple_string
optional_id
optional_context_mod
music
| "\new"
simple_string
optional_id
optional_context_mod
music
| "\times" fraction music
| repeated_music
| "\transpose"
pitch_also_in_chords
pitch_also_in_chords
music
| mode_changing_head grouped_music_list
| mode_changing_head_with_context
optional_context_mod
grouped_music_list
| relative_music
| re_rhythmed_music
'''
# pylint: disable=redefined-builtin
def __init__(self, type=None, genericPrefixMusicScm=None,
simpleString=None, optionalId=None, optionalContextMod=None,
music=None, fraction=None, repeatedMusic=None,
pitchAlsoInChords1=None, pitchAlsoInChords2=None,
modeChangingHead=None, groupedMusicList=None,
modeChangingHeadWithContext=None, relativeMusic=None,
reRhythmedMusic=None
):
super().__init__()
self.type = type
self.genericPrefixMusicScm = genericPrefixMusicScm
self.simpleString = simpleString
self.optionalId = optionalId
self.optionalContextMod = optionalContextMod
self.music = music
self.fraction = fraction
self.repeatedMusic = repeatedMusic
self.pitchAlsoInChords1 = pitchAlsoInChords1
self.pitchAlsoInChords2 = pitchAlsoInChords2
self.modeChangingHead = modeChangingHead
self.groupedMusicList = groupedMusicList
self.modeChangingHeadWithContext = modeChangingHeadWithContext
self.relativeMusic = relativeMusic
self.reRhythmedMusic = reRhythmedMusic
[docs]
def stringOutput(self):
myType = self.type
if myType == 'scheme':
return str(self.genericPrefixMusicScm)
elif myType in ('context', 'new'):
c = self.backslash + myType + ' ' + str(self.simpleString) + ' '
if self.optionalId is not None:
c += str(self.optionalId) + ' '
if self.optionalContextMod is not None:
c += str(self.optionalContextMod) + ' '
c += str(self.music) + ' '
return c
elif myType == 'times':
return self.backslash + 'times ' + str(self.fraction) + ' ' + str(self.music) + ' '
elif myType == 'repeated':
return str(self.repeatedMusic)
elif myType == 'transpose':
return ''.join([self.backslash, 'transpose ', str(self.pitchAlsoInChords1), ' ',
str(self.pitchAlsoInChords2), ' ', str(self.music), ' '])
elif myType == 'modeChanging':
return str(self.modeChangingHead) + ' ' + str(self.groupedMusicList)
elif myType == 'modeChangingWith':
c = str(self.modeChangingHeadWithContext) + ' '
if self.optionalContextMod is not None:
c += str(self.optionalContextMod) + ' '
c += str(self.groupedMusicList) + ' '
return c
elif myType == 'relative':
return str(self.relativeMusic)
elif myType == 'rhythmed':
return str(self.reRhythmedMusic)
else: # pragma: no cover
raise LilyObjectsException(f'unknown self.type or None: {self.type}')
[docs]
class LyModeChangingHead(LyObject):
r'''
represents both mode_changing_head and mode_changing_head_with_context
.hasContext = False
.mode = ['note', 'drum', 'figure', 'chord', 'lyric']
>>> l = lily.lilyObjects.LyModeChangingHead(hasContext=True, mode='drum')
>>> print(l.stringOutput())
\drummode
>>> l2 = lily.lilyObjects.LyModeChangingHead(hasContext=False, mode='chord')
>>> print(l2.stringOutput())
\chords
'''
allowableModes = ['note', 'drum', 'figure', 'chord', 'lyric']
def __init__(self, hasContext=False, mode=None):
super().__init__()
self.hasContext = hasContext
self.mode = mode
[docs]
def stringOutput(self):
if self.mode is None:
raise LilyObjectsException('Mode must be set') # pragma: no cover
if self.mode not in self.allowableModes:
raise LilyObjectsException(f'Not an allowable mode {self.mode}') # pragma: no cover
if self.hasContext:
return self.backslash + self.mode + 'mode'
else:
return self.backslash + self.mode + 's'
[docs]
class LyRelativeMusic(LyObject):
r'''
relative music
'''
def __init__(self, content=None):
super().__init__()
self.content = content
[docs]
def stringOutput(self):
return self.backslash + 'relative ' + self.content.stringOutput()
[docs]
class LyNewLyrics(LyObject):
r'''
contains a list of LyGroupedMusicList objects or identifiers
'''
def __init__(self, groupedMusicLists=None):
if groupedMusicLists is None:
groupedMusicLists = []
super().__init__()
self.groupedMusicLists = groupedMusicLists
[docs]
def stringOutput(self):
outputString = ''
for c in self.groupedMusicLists:
outputString += self.backslash + 'addlyrics '
if hasattr(c, 'stringOutput'):
outputString += c.stringOutput()
else:
outputString += c + ' '
return outputString
[docs]
class LyReRhythmedMusic(LyObject):
def __init__(self, groupedMusic=None, newLyrics=None):
super().__init__()
self.groupedMusic = groupedMusic
self.newLyrics = newLyrics
[docs]
def stringOutput(self):
c = self.groupedMusic
if hasattr(c, 'stringOutput'):
outputString = c.stringOutput()
else:
outputString = c + ' '
outputString += self.newLyrics.stringOutput()
return outputString # previously this did not return
[docs]
class LyContextChange(LyObject):
r'''
>>> lcc = lily.lilyObjects.LyContextChange('x', 'y')
>>> str(lcc)
'\\change x = y '
'''
def __init__(self, before=None, after=None):
super().__init__()
self.before = before
self.after = after
[docs]
def stringOutput(self):
return self.backslash + 'change ' + self.before + ' = ' + self.after + ' '
[docs]
class LyPropertyPath(LyObject):
r'''
represents both property_path and property_path_revved
has one or more of LyEmbeddedScm objects
'''
def __init__(self, embeddedScheme=None):
if embeddedScheme is None:
embeddedScheme = []
super().__init__()
self.embeddedScheme = embeddedScheme
[docs]
def stringOutput(self):
return ' '.join([es.stringOutput() for es in self.embeddedScheme])
[docs]
class LyPropertyOperation(LyObject):
r'''
Represents:
property_operation: STRING '=' scalar
| "\unset" simple_string
| "\override" simple_string property_path '=' scalar
| "\revert" simple_string embedded_scm
mandatory mode in ['set', 'unset', 'override', 'revert']
also represents simple_music_property_def which has the same forms
>>> lpo = lily.lilyObjects.LyPropertyOperation('unset', 'simple')
>>> str(lpo)
'\\unset simple '
>>> lpo = lily.lilyObjects.LyPropertyOperation('override', 'simple', 'x', 'y')
>>> str(lpo)
'\\override simple.x = y '
>>> lpo = lily.lilyObjects.LyPropertyOperation('revert', 'x', 'y')
>>> str(lpo)
'\\revert x.y '
TODO: should \set be given?
'''
def __init__(self, mode=None, value1=None, value2=None, value3=None):
super().__init__()
self.mode = mode
self.value1 = value1
self.value2 = value2
self.value3 = value3
[docs]
def stringOutput(self):
if self.mode not in ('set', 'unset', 'override', 'revert'):
raise LilyObjectsException(f'invalid mode {self.mode}')
if self.mode == 'set':
return self.backslash + 'set ' + self.value1 + ' = ' + self.value2 + ' '
elif self.mode == 'unset':
return self.backslash + 'unset ' + self.value1 + ' '
elif self.mode == 'override':
return ''.join([self.backslash, 'override ', self.value1, '.', self.value2,
' = ', self.value3, ' '])
elif self.mode == 'revert':
return self.backslash + 'revert ' + self.value1 + '.' + self.value2 + ' '
[docs]
class LyContextDefMod(LyObject):
r'''
one of consists, remove, accepts, defaultchild, denies, alias, type, description, name
'''
def __init__(self, contextDef=None):
super().__init__()
self.contextDef = contextDef
[docs]
def stringOutput(self):
return self.backslash + self.contextDef + ' '
[docs]
class LyContextMod(LyObject):
def __init__(self, contextDefOrProperty=None, scalar=None):
super().__init__()
self.contextDefOrProperty = contextDefOrProperty
self.scalar = scalar
[docs]
def stringOutput(self):
if self.scalar is None:
return self.contextDefOrProperty.stringOutput()
else:
return self.contextDefOrProperty.stringOutput() + ' ' + self.scalar + ' '
# no need for context_prop_spec -- just strings
# see LyPropertyOperation for simple_music_property_def
[docs]
class LyMusicPropertyDef(LyObject):
def __init__(self, isOnce=False, propertyDef=None):
super().__init__()
self.isOnce = isOnce
self.propertyDef = propertyDef
[docs]
def stringOutput(self):
s = ''
if self.isOnce:
s += self.backslash + 'once '
return s + self.propertyDef.stringOutput()
# string, simple_string, scalar, etc. not needed
[docs]
class LyEventChord(LyObject):
r'''
takes all the parts as a list of up to three elements::
event_chord: simple_chord_elements post_events
| CHORD_REPETITION optional_notemode_duration post_events
| MULTI_MEASURE_REST optional_notemode_duration post_events
| command_element
| note_chord_element
simple_chord_elements can be a LySimpleElement object. Or it can be a
LyNewChord or LyFigureSpec + Duration
once that is done. But there is no LySimpleChordElements object yet.
'''
def __init__(self, simpleChordElements=None, postEvents=None, chordRepetition=None,
multiMeasureRest=None, duration=None, commandElement=None, noteChordElement=None):
super().__init__()
self.simpleChordElements = simpleChordElements
self.postEvents = postEvents
self.chordRepetition = chordRepetition
self.multiMeasureRest = multiMeasureRest
self.duration = duration
self.commandElement = commandElement
self.noteChordElement = noteChordElement
[docs]
def stringOutput(self):
if self.noteChordElement is not None:
return str(self.noteChordElement) + ' '
elif self.commandElement is not None:
return str(self.commandElement) + ' '
elif self.multiMeasureRest is not None:
c = str(self.multiMeasureRest)
if self.duration is not None:
c += ' ' + self.duration
if self.postEvents is not None:
for pe in self.postEvents:
c += str(pe)
c += ' '
return c
elif self.chordRepetition is not None:
c = str(self.chordRepetition)
if self.duration is not None:
c += ' ' + self.duration
if self.postEvents is not None:
for pe in self.postEvents:
c += ' ' + str(pe)
c += ' '
return c
else:
c = str(self.simpleChordElements)
if self.postEvents is not None:
for pe in self.postEvents:
c += ' ' + str(pe)
c += ' '
return c
[docs]
class LyNoteChordElement(LyObject):
def __init__(self, chordBody=None, optionalNoteModeDuration=None, postEvents=None):
if postEvents is None:
postEvents = []
super().__init__()
self.chordBody = chordBody
self.optionalNoteModeDuration = optionalNoteModeDuration
self.postEvents = postEvents
[docs]
def stringOutput(self):
c = str(self.chordBody)
if self.optionalNoteModeDuration is not None:
c += str(self.optionalNoteModeDuration) + ' '
for pe in self.postEvents:
c += str(pe) + ' '
return c
[docs]
class LyChordBody(LyObject):
def __init__(self, chordBodyElements=None):
if chordBodyElements is None:
chordBodyElements = []
super().__init__()
self.chordBodyElements = chordBodyElements
[docs]
def stringOutput(self):
c = ' '.join([str(cbe) for cbe in self.chordBodyElements])
return ' '.join(['<', c, '> '])
[docs]
class LyChordBodyElement(LyObject):
r'''
Contains a note or a drum pitch or a music function::
chord_body_element: pitch
exclamations (a string of zero or more ! marks)
questions (a string of zero or more ? marks)
octave_check
post_events
| DRUM_PITCH post_events
| music_function_chord_body
TODO: only the first form is currently supported in creation
'''
def __init__(self, parts=None):
if parts is None:
parts = []
super().__init__()
self.parts = parts
[docs]
def stringOutput(self):
return ' '.join([str(p) for p in self.parts])
# music_function_identifier_musicless_prefix: MUSIC_FUNCTION
# NOT Supported
# 217 music_function_chord_body: music_function_identifier_musicless_prefix
# EXPECT_MUSIC
# function_arglist_nonmusic
# chord_body_element
# 218 | music_function_identifier_musicless_prefix
# function_arglist_nonmusic
#
# 219 music_function_event: music_function_identifier_musicless_prefix
# EXPECT_MUSIC
# function_arglist_nonmusic
# post_event
# 220 | music_function_identifier_musicless_prefix
# function_arglist_nonmusic
[docs]
class LyCommandElement(LyObject):
def __init__(self, commandType=None, argument=None):
super().__init__()
self.commandType = commandType
self.argument = argument
[docs]
def stringOutput(self):
ct = self.commandType
if ct == 'skip':
return self.backslash + 'skip ' + self.argument.stringOutput()
elif ct == '[':
return self.backslash + '[ '
elif ct == ']':
return self.backslash + '] '
elif ct == self.backslash:
return ct + ' '
elif ct == 'partial':
return self.backslash + 'partial ' + self.argument.stringOutput()
elif ct == 'time':
return self.backslash + 'time ' + self.argument + ' '
elif ct == 'mark':
return self.backslash + 'mark ' + self.argument + ' '
else:
return ct.stringOutput()
[docs]
class LyCommandEvent(LyObject):
def __init__(self, commandType=None, argument1=None, argument2=None):
super().__init__()
self.commandType = commandType
self.argument1 = argument1
self.argument2 = argument2
[docs]
def stringOutput(self):
ct = self.commandType
if ct == '~': # ??? not tie?
return self.backslash + '~ '
elif ct == 'mark-default':
return self.backslash + 'mark ' + self.backslash + 'default '
elif ct == 'key-default':
return self.backslash + 'key ' + self.backslash + 'default '
elif ct == 'key':
# \key NOTENAME_PITCH SCM_IDENTIFIER
return self.backslash + 'key ' + self.argument1 + ' ' + self.argument2 + ' '
else: # tempo_event
return ct.stringOutput()
[docs]
class LyPostEvents(LyObject):
def __init__(self, eventList=None):
if eventList is None:
eventList = []
super().__init__()
self.eventList = eventList
[docs]
def stringOutput(self):
return ' '.join([e.stringOutput() for e in self.eventList])
[docs]
class LyPostEvent(LyObject):
def __init__(self, arg1=None, arg2=None):
super().__init__()
self.arg1 = arg1
self.arg2 = arg2
[docs]
def stringOutput(self):
c = str(self.arg1)
if self.arg2 is not None:
c += ' ' + str(self.arg2)
return c + ' '
[docs]
class LyDirectionLessEvent(LyObject):
r'''
represents ['[', ']', '~', '(', ')', '\!', '\(', '\)', '\>', '\<']
or an EVENT_IDENTIFIER or a tremolo_type
'''
def __init__(self, event=None):
super().__init__()
self.event = event
[docs]
def stringOutput(self):
return str(self.event) + ' '
# noinspection SpellCheckingInspection
[docs]
class LyDirectionReqdEvent(LyObject):
def __init__(self, event=None):
super().__init__()
self.event = event
[docs]
def stringOutput(self):
return str(self.event) + ' '
[docs]
class LyOctaveCheck(LyObject):
def __init__(self, equalOrQuotesOrNone=None):
super().__init__()
self.equalOrQuotesOrNone = equalOrQuotesOrNone
[docs]
def stringOutput(self):
eqn = self.equalOrQuotesOrNone
if eqn is None:
return None
elif eqn == '=':
return '= '
else:
return '= ' + eqn + ' '
[docs]
class LyPitch(LyObject):
r'''
represents a pitch name and zero or more sup or sub quotes
also used for steno_pitch and steno_tonic_pitch
'''
def __init__(self, noteNamePitch=None, quotes=None):
super().__init__()
self.noteNamePitch = noteNamePitch
self.quotes = quotes
[docs]
def stringOutput(self):
return self.noteNamePitch + str(self.quotes) + ' '
# no need for pitch_also_in_chords
[docs]
class LyGenTextDef(LyObject):
r'''
holds either full_markup, string, or DIGIT
'''
def __init__(self, value=None):
super().__init__()
self.value = value
[docs]
def stringOutput(self):
return str(self.value) + ' '
[docs]
class LyScriptAbbreviation(LyObject):
r'''
Holds a script abbreviation (for articulations etc.), one of::
^ + - | > . _
'''
def __init__(self, value=None):
super().__init__()
self.value = value
[docs]
def stringOutput(self):
return str(self.value) + ' '
[docs]
class LyScriptDir(LyObject):
r'''
Holds a script direction abbreviation (above below etc), one of::
_ ^ -
'''
def __init__(self, value=None):
super().__init__()
self.value = value
[docs]
def stringOutput(self):
return str(self.value) + ' '
# no need for absolute_pitch
# no need for optional_notemode_duration -- we can use LyMultipliedDuration or None
[docs]
class LyStenoDuration(LyObject):
r'''
the main thing that we think of as non-tuplet duration.
a duration number followed by one or more dots
>>> lsd = lily.lilyObjects.LyStenoDuration('2', 2)
>>> print(lsd)
2..
'''
def __init__(self, durationNumber=None, numDots=0):
super().__init__()
self.durationNumber = durationNumber
self.numDots = numDots
[docs]
def stringOutput(self):
dotStr = '.' * self.numDots
return str(self.durationNumber) + dotStr + ' '
[docs]
class LyMultipliedDuration(LyObject):
r'''
represents either a simple LyStenoDuration or a list of things that
the steno duration should be multiplied by.
if stenoDur is None then output is None -- thus also represents
optional_notemode_duration
'''
def __init__(self, stenoDur=None, multiply=None):
if multiply is None:
multiply = []
super().__init__()
self.stenoDur = stenoDur
self.multiply = multiply
[docs]
def stringOutput(self):
if self.stenoDur is None:
return None
else:
s = str(self.stenoDur)
for m in self.multiply:
s += ' * ' + str(m)
return s
[docs]
class LyTremoloType(LyObject):
def __init__(self, tremTypeOrNone=None):
super().__init__()
self.tremTypeOrNone = tremTypeOrNone
[docs]
def stringOutput(self):
if self.tremTypeOrNone is not None:
return ':' + str(self.tremTypeOrNone) + ' '
else:
return ': '
# SKIPPING figured bass objects (lines 305 - 325) for now
[docs]
class LyOptionalRest(LyObject):
def __init__(self, rest=False):
super().__init__()
self.rest = rest
[docs]
def stringOutput(self):
if self.rest is False:
return ''
else:
return self.backslash + 'rest '
[docs]
class LySimpleElement(LyObject):
r'''
A single note, lyric element, drum pitch or hidden rest::
simple_element: pitch
exclamations (a string of zero or more ! marks)
questions (a string of zero or more ? marks)
octave_check
optional_notemode_duration
optional_rest
| DRUM_PITCH optional_notemode_duration
| RESTNAME optional_notemode_duration
| lyric_element optional_notemode_duration
'''
def __init__(self, parts=None):
if parts is None:
parts = []
super().__init__()
self.parts = parts
[docs]
def stringOutput(self):
return ''.join([str(p) for p in self.parts])
# SKIPPING ALL ChordSymbol Markup for now
[docs]
class LyLyricElement(LyObject):
r'''
Object represents a single Lyric in lilypond.
>>> lle = lily.lilyObjects.LyLyricElement('hel_')
>>> lle
<music21.lily.lilyObjects.LyLyricElement hel_>
>>> print(lle)
hel_
'''
def __init__(self, lyMarkupOrString=None):
super().__init__()
self.lyMarkupOrString = lyMarkupOrString
[docs]
def stringOutput(self):
return str(self.lyMarkupOrString) + ' '
[docs]
class LyTempoRange(LyObject):
r'''
defines either a single tempo or a range
'''
def __init__(self, lowestOrOnlyTempo=None, highestTempoOrNone=None):
super().__init__()
self.lowestOrOnlyTempo = lowestOrOnlyTempo
self.highestTempoOrNone = highestTempoOrNone
[docs]
def stringOutput(self):
if self.highestTempoOrNone is None:
return str(self.lowestOrOnlyTempo) + ' '
else:
return str(self.lowestOrOnlyTempo) + '~' + str(self.highestTempoOrNone) + ' '
[docs]
class LyNumberExpression(LyObject):
r'''
any list of numbers or LyNumberTerms separated by '+' or '-' objects.
'''
def __init__(self, numberAndSepList=None):
if numberAndSepList is None:
numberAndSepList = []
super().__init__()
self.numberAndSepList = numberAndSepList
[docs]
def stringOutput(self):
c = ' '.join([str(s) for s in self.numberAndSepList])
return c + ' '
[docs]
class LyNumberTerm(LyObject):
r'''
any list of numbers separated by '*' or '/' strings.
'''
def __init__(self, numberAndSepList=None):
if numberAndSepList is None:
numberAndSepList = []
super().__init__()
self.numberAndSepList = numberAndSepList
[docs]
def stringOutput(self):
c = ' '.join([str(s) for s in self.numberAndSepList])
return c + ' '
[docs]
class LyLyricMarkup(LyObject):
def __init__(self, lyricMarkupOrIdentifier=None, markupTop=None):
super().__init__()
self.lyricMarkupOrIdentifier = lyricMarkupOrIdentifier
self.markupTop = markupTop
[docs]
def stringOutput(self):
if self.markupTop is None:
return str(self.lyricMarkupOrIdentifier) + ' '
else:
return str(self.lyricMarkupOrIdentifier) + ' ' + str(self.markupTop)
[docs]
class LyFullMarkupList(LyObject):
def __init__(self, markupListOrIdentifier=None):
super().__init__()
self.markupListOrIdentifier = markupListOrIdentifier
[docs]
def stringOutput(self):
if isinstance(self.markupListOrIdentifier, str):
return self.markupListOrIdentifier + ' '
else:
# noinspection SpellCheckingInspection
return self.backslash + 'markuplines ' + self.markupListOrIdentifier.stringOutput()
[docs]
class LyFullMarkup(LyObject):
def __init__(self, markupTopOrIdentifier=None):
super().__init__()
self.markupTopOrIdentifier = markupTopOrIdentifier
[docs]
def stringOutput(self):
if isinstance(self.markupTopOrIdentifier, str):
return self.markupTopOrIdentifier + ' '
else:
return self.backslash + 'markup ' + self.markupTopOrIdentifier.stringOutput()
[docs]
class LyMarkupTop(LyObject):
def __init__(self, argument1=None, argument2=None):
super().__init__()
self.argument1 = argument1
self.argument2 = argument2
[docs]
def stringOutput(self):
if self.argument2 is None:
return str(self.argument1)
else:
return ' '.join([self.argument1, self.argument2])
[docs]
class LyMarkupList(LyObject):
def __init__(self, markupIdentifierOrList=None):
super().__init__()
self.markupIdentifierOrList = markupIdentifierOrList
[docs]
def stringOutput(self):
return str(self.markupIdentifierOrList)
[docs]
class LyMarkupComposedList(LyObject):
def __init__(self, markupHeadList=None, markupBracedList=None):
super().__init__()
self.markupHeadList = markupHeadList
self.markupBracedList = markupBracedList
[docs]
def stringOutput(self):
return ' '.join([self.markupHeadList, self.markupBracedList])
[docs]
class LyMarkupBracedList(LyObject):
def __init__(self, listBody=None):
super().__init__()
self.listBody = listBody
[docs]
def stringOutput(self):
return ' '.join(['{', self.listBody, '}'])
[docs]
class LyMarkupBracedListBody(LyObject):
def __init__(self, markupOrMarkupList=None):
if markupOrMarkupList is None:
markupOrMarkupList = []
super().__init__()
self.markupOrMarkupList = markupOrMarkupList
[docs]
def stringOutput(self):
super().__init__()
c = ''
for m in self.markupOrMarkupList:
c += str(m) + ' '
return c
# skip markup_command_list and arguments for now
# skip markup_head_1_item
# skip markup_head_1_list
# simple_markup can be string or more complex
[docs]
class LySimpleMarkup(LyObject):
r'''
simpleType can be 'string' (or markup identifier or lyric markup identifier, etc.) or
'score-body' or 'markup-function'
takes 1 required arg, 2nd for markup_function
'''
def __init__(self, simpleType='string', argument1=None, argument2=None):
super().__init__()
self.simpleType = simpleType
self.argument1 = argument1
self.argument2 = argument2
[docs]
def stringOutput(self):
if self.simpleType == 'string':
return self.argument1 + ' '
elif self.simpleType == 'score-body':
return self.backslash + 'score { ' + self.argument1 + ' } '
elif self.simpleType == 'markup-function':
return self.argument1 + ' ' + str(self.argument2) + ' '
[docs]
class LyMarkup(LyObject):
def __init__(self, simpleMarkup=None, optionalMarkupHeadList=None):
super().__init__()
self.simpleMarkup = simpleMarkup
self.optionalMarkupHeadList = optionalMarkupHeadList
[docs]
def stringOutput(self):
if self.optionalMarkupHeadList is not None:
c = self.optionalMarkupHeadList + ' '
else:
c = ''
return c + str(self.simpleMarkup)
# ------------older-------------
#
# class LyNote(LyObject):
# pass
#
# class LyDuration(LyObject):
# pass
#
# class LyLyricGroup(LyObject):
# pass
#
# ## --------Layout----------##
#
# class LyPaper(LyObject):
# m21toLy = {'PageLayout': {'pageWidth': 'paper-width',
# 'pageHeight': 'paper-height',
# 'topMargin': 'top-margin',
# 'bottomMargin': 'bottom-margin',
# 'leftMargin': 'left-margin',
# 'rightMargin': 'right-margin',
# },
# }
#
# defaultAttributes = {'pageWidth': None,
# 'pageHeight': None,
# 'topMargin': None,
# 'bottomMargin': None,
# 'leftMargin': None,
# 'rightMargin': None,
# }
#
#
# class LyLayout(LyObject):
# pass
#
#
# ## -------Tools-----------##
# class LyCodePrinter:
# pass
#
# def __init__(self):
# currentIndent = 0
# bracketNesting = 0
# angleBracketNest = 0
# # ------Tests------------##
class Test(unittest.TestCase):
def testOneNoteTheHardWay(self):
r'''
make a dotted-half note c.
'''
lyPitch = LyPitch('c', "''")
stenoDuration = LyStenoDuration('2', 1)
multipliedDuration = LyMultipliedDuration(stenoDuration)
simpleElement = LySimpleElement(parts=[lyPitch, multipliedDuration])
eventChord = LyEventChord(simpleElement)
simpleMusic = LySimpleMusic(eventChord=eventChord)
musicInner = LyMusic(simpleMusic=simpleMusic)
musicList = LyMusicList([musicInner])
sequentialMusic = LySequentialMusic(musicList)
compositeMusic = LyCompositeMusic(groupedMusicList=sequentialMusic)
lilypondTop = LyLilypondTop([compositeMusic])
lilypondOutput = lilypondTop.stringOutput()
self.assertEqual(lilypondOutput.strip(), "{ c'' 2. \n }")
ancestors = []
for n in lyPitch.ancestorList():
ancestors.append(n.__class__.__name__)
self.assertEqual(ancestors, ['LySimpleElement', 'LyEventChord', 'LySimpleMusic', 'LyMusic',
'LyMusicList', 'LySequentialMusic', 'LyCompositeMusic',
'LyLilypondTop'])
ancestorCompositeMusic = lyPitch.getAncestorByClass(LyCompositeMusic)
self.assertIs(ancestorCompositeMusic, compositeMusic)
if __name__ == '__main__':
import music21
music21.mainTest(Test)