Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions music21/lily/lilyObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,23 +867,34 @@ class LyTempoEvent(LyObject):
r'''
tempo_event: "\tempo" steno_duration '=' tempo_range
| "\tempo" scalar steno_duration '=' tempo_range
| "\tempo" steno_duration '=' scalar
| "\tempo" scalar

>>> lte = lily.lilyObjects.LyTempoEvent(scalar='40')
>>> str(lte)
'\\tempo 40'

More complex:
A steno_duration paired with a single bpm scalar (and no tempoRange)
is the common case for a music21 MetronomeMark, e.g. quarter = 87:

>>> steno = lily.lilyObjects.LyStenoDuration('quarter')
>>> steno = lily.lilyObjects.LyStenoDuration('4')
>>> lte = lily.lilyObjects.LyTempoEvent(stenoDuration=steno, scalar=87)
>>> str(lte)
'\\tempo 4 = 87'

More complex, with a tempo range. Note that steno_duration takes a
Lilypond duration number such as '4' for a quarter note, not the
English name 'quarter':

>>> steno = lily.lilyObjects.LyStenoDuration('4')
>>> tempoRange = lily.lilyObjects.LyTempoRange(70, 100)
>>> lte = lily.lilyObjects.LyTempoEvent(tempoRange=tempoRange, stenoDuration=steno)
>>> str(lte)
'\\tempo quarter = 70~100 '
'\\tempo 4 = 70~100 '

>>> lte.scalar = 85
>>> str(lte)
'\\tempo 85 quarter = 70~100 '
'\\tempo 85 4 = 70~100 '
'''

def __init__(self, tempoRange=None, stenoDuration=None, scalar=None):
Expand All @@ -906,6 +917,11 @@ def stringOutput(self):
else:
return ' '.join([base, self.stenoDuration.stringOutput(),
'=', self.tempoRange.stringOutput()])
elif self.stenoDuration is not None:
if self.scalar is None: # pragma: no cover
raise LilyObjectsException(
'If tempoRange is not defined but stenoDuration is, need a scalar')
return ' '.join([base, self.stenoDuration.stringOutput(), '=', str(self.scalar)])
elif self.scalar is None: # pragma: no cover
raise LilyObjectsException('If tempoRange is not defined then need scalar')

Expand Down
59 changes: 59 additions & 0 deletions music21/lily/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from music21 import key
from music21 import note
from music21 import stream
from music21 import tempo
from music21 import variant

from music21.lily import lilyObjects as lyo
Expand Down Expand Up @@ -1096,6 +1097,11 @@ def appendM21ObjectToContext(self, thisObject):
lyObject = self.lyEmbeddedScmFromTimeSignature(thisObject)
currentMusicList.append(lyObject)
lyObject.setParent(contextObject)
elif 'MetronomeMark' in c:
lyObject = self.lyEmbeddedScmFromMetronomeMark(thisObject)
if lyObject is not None:
currentMusicList.append(lyObject)
lyObject.setParent(contextObject)
elif 'Variant' in c:
self.appendContextFromVariant(thisObject, coloredVariants=self.coloredVariants)
elif 'SystemLayout' in c:
Expand Down Expand Up @@ -1680,6 +1686,38 @@ def lyEmbeddedScmFromTimeSignature(self, ts):
lpEmbeddedScm.content = keyScheme
return lpEmbeddedScm

def lyEmbeddedScmFromMetronomeMark(self, mm: tempo.MetronomeMark) -> lyo.LyEmbeddedScm|None:
# noinspection PyShadowingNames
r'''
convert a :class:`~music21.tempo.MetronomeMark` object
to a lilyObjects.LyEmbeddedScm object

* New in v11: MetronomeMark objects are now written out when
converting a Stream to Lilypond; previously they were silently dropped.

>>> mm = tempo.MetronomeMark(number=87, referent=note.Note(type='quarter'))
>>> conv = lily.translate.LilypondConverter()
>>> print(conv.lyEmbeddedScmFromMetronomeMark(mm))
\tempo 4 = 87

A MetronomeMark without a number produces no output:

>>> mm = tempo.MetronomeMark()
>>> mm.number is None
True
>>> conv.lyEmbeddedScmFromMetronomeMark(mm) is None
True
'''
if mm.number is None:
return None

multipliedDuration = self.lyMultipliedDurationFromDuration(mm.referent)
tempoEvent = lyo.LyTempoEvent(stenoDuration=multipliedDuration.stenoDur, scalar=mm.number)

lpEmbeddedScm = lyo.LyEmbeddedScm()
lpEmbeddedScm.content = tempoEvent.stringOutput() + lpEmbeddedScm.newlineIndent
return lpEmbeddedScm

def setContextForTupletStart(self, inObj):
r'''
if the inObj has tuplets then we set a new context
Expand Down Expand Up @@ -2596,6 +2634,27 @@ def testColors(self):
"c' 4 "
)

def testMetronomeMark(self):
mm = tempo.MetronomeMark(number=87, referent=note.Note(type='quarter'))
lpEmbeddedScm = LilypondConverter().lyEmbeddedScmFromMetronomeMark(mm)
self.assertEqual(str(lpEmbeddedScm).strip(), r'\tempo 4 = 87')

# a MetronomeMark with no number produces no output
textOnly = tempo.MetronomeMark()
self.assertIsNone(LilypondConverter().lyEmbeddedScmFromMetronomeMark(textOnly))

def testMetronomeMarkWrittenInStream(self):
# https://github.com/cuthbertLab/music21/issues/1852
from music21 import key
from music21 import meter
keysig = key.Key('a-')
mm = tempo.MetronomeMark(number=87, referent=note.Note(type='quarter'))
timesig = meter.TimeSignature('3/4')
s = stream.Stream([keysig, mm, timesig, note.Note()])
lpc = LilypondConverter()
lpc.loadObjectFromScore(s, makeNotation=False)
self.assertIn(r'\tempo 4 = 87', str(lpc.topLevelObject))

class TestExternal(unittest.TestCase):
show = True

Expand Down
Loading