• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cuthbertLab / music21 / 16841233622

08 Aug 2025 09:50PM UTC coverage: 92.998%. Remained the same
16841233622

Pull #1800

github

web-flow
Merge ed9f50f36 into eb5a88fe8
Pull Request #1800: Update of property assignment syntax for Lilypond conversion (issue #1791)

4 of 5 new or added lines in 2 files covered. (80.0%)

1 existing line in 1 file now uncovered.

81122 of 87230 relevant lines covered (93.0%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.98
/music21/lily/translate.py
1
# -*- coding: utf-8 -*-
2
# ------------------------------------------------------------------------------
3
# Name:         lily/translate.py
4
# Purpose:      music21 classes for translating to Lilypond
5
#
6
# Authors:      Michael Scott Asato Cuthbert
7
#
8
# Copyright:    Copyright © 2007-2024 Michael Scott Asato Cuthbert
9
# License:      BSD, see license.txt
10
# ------------------------------------------------------------------------------
11
'''
12
music21 translates to Lilypond format and if Lilypond is installed on the
13
local computer, can automatically generate .pdf, .png, and .svg versions
14
of musical files using Lilypond.
15
'''
16
from __future__ import annotations
1✔
17

18
from collections import OrderedDict
1✔
19
from importlib.util import find_spec
1✔
20
import os
1✔
21
import pathlib
1✔
22
import re
1✔
23
import subprocess
1✔
24
import sys
1✔
25
import unittest
1✔
26

27
from music21 import clef
1✔
28
from music21 import common
1✔
29
from music21.converter.subConverters import SubConverter
1✔
30
from music21 import corpus
1✔
31
from music21 import duration
1✔
32
from music21 import environment
1✔
33
from music21 import exceptions21
1✔
34
from music21 import key
1✔
35
from music21 import note
1✔
36
from music21 import stream
1✔
37
from music21 import variant
1✔
38

39
from music21.lily import lilyObjects as lyo
1✔
40

41
environLocal = environment.Environment('lily.translate')
1✔
42

43
try:
1✔
44
    if find_spec('PIL.Image') and find_spec('PIL.ImageOps'):
1✔
45
        noPIL = False
1✔
46
    else:  # pragma: no cover
47
        noPIL = True
48
except ModuleNotFoundError:  # pragma: no cover
49
    noPIL = True
50

51
del find_spec
1✔
52

53
# TODO: speed up tests everywhere! move these to music21 base
54

55
class _sharedCorpusTestObject:
1✔
56
    sharedCache: dict[str, stream.Stream] = {}
1✔
57

58

59
sharedCacheObject = _sharedCorpusTestObject()
1✔
60

61

62
def _getCachedCorpusFile(keyName):
1✔
63
    # return corpus.parse(keyName)
64
    if keyName not in sharedCacheObject.sharedCache:
1✔
65
        sharedCacheObject.sharedCache[keyName] = corpus.parse(keyName)
1✔
66
    return sharedCacheObject.sharedCache[keyName]
1✔
67

68
# b.parts[0].measure(4)[2].color = 'blue'#.rightBarline = 'double'
69

70

71
def makeLettersOnlyId(inputString):
1✔
72
    # noinspection SpellCheckingInspection
73
    r'''
74
        Takes an id and makes it purely letters by substituting
75
        letters for all other characters.
76

77

78
        >>> print(lily.translate.makeLettersOnlyId('rainbow123@@dfas'))
79
        rainbowxyzmmdfas
80
        '''
81
    inputString = str(inputString)
1✔
82
    returnString = ''
1✔
83
    for c in inputString:
1✔
84
        if not c.isalpha():
1✔
85
            c = chr(ord(c) % 26 + 97)
1✔
86
        returnString += c
1✔
87

88
    return returnString
1✔
89

90
# ------------------------------------------------------------------------------
91

92

93
class LilypondConverter:
1✔
94
    fictaDef = (
1✔
95
        r'''
96
    ficta = #(define-music-function (parser location) () #{ \once \set suggestAccidentals = ##t #})
97
    '''.lstrip())
98
    colorDef = (
1✔
99
        r'''
100
    color = #(define-music-function (parser location color) (string?) #{
101
        \once \override NoteHead.color = #(x11-color color)
102
        \once \override Stem.color = #(x11-color color)
103
        \once \override Rest.color = #(x11-color color)
104
        \once \override Beam.color = #(x11-color color)
105
     #})
106
    '''.lstrip())
107
    simplePaperDefinitionScm = r'''
1✔
108
    \paper { #(define dump-extents #t)
109
    indent = 0\mm
110
    force-assignment = #""
111
    oddFooterMarkup=##f
112
    oddHeaderMarkup=##f
113
    bookTitleMarkup=##f
114
    }
115
    '''.lstrip()
116
    transparencyStartScheme = r'''
1✔
117
     \override Rest.transparent  = ##t
118
     \override Dots.transparent  = ##t
119
     '''.lstrip()
120
    transparencyStopScheme = r'''
1✔
121
     \revert Rest #'transparent
122
     \revert Dots #'transparent
123
     '''.lstrip()
124
    bookHeader = r'''
1✔
125
    \include "lilypond-book-preamble.ly"
126
    '''.lstrip()
127

128
    accidentalConvert = {'double-sharp': 'isis',
1✔
129
                         'double-flat': 'eses',
130
                         'one-and-a-half-sharp': 'isih',
131
                         'one-and-a-half-flat': 'eseh',
132
                         'sharp': 'is',
133
                         'flat': 'es',
134
                         'half-sharp': 'ih',
135
                         'half-flat': 'eh',
136
                         }
137

138
    barlineDict = {'regular': '|',
1✔
139
                   'dotted': ':',
140
                   'dashed': 'dashed',
141
                   'heavy': '.',  # ??
142
                   'double': '||',
143
                   'final': '|.',
144
                   'heavy-light': '.|',
145
                   'heavy-heavy': '.|.',
146
                   'start-repeat': '|:',
147
                   'end-repeat': ':|',
148
                   # no music21 support for |.| lightHeavyLight yet
149
                   'tick': "'",
150
                   # 'short': '',  # no lilypond support??
151
                   'none': '',
152
                   }
153

154
    def __init__(self):
1✔
155
        self.majorVersion = '1'
1✔
156
        self.minorVersion = '0'
1✔
157
        self.versionString = '1.0'
1✔
158
        self.backend = 'ps'
1✔
159
        self.versionScheme = ''
1✔
160
        self.headerScheme = ''
1✔
161
        self.backendString = '--backend='
1✔
162

163
        self.topLevelObject = lyo.LyLilypondTop()
1✔
164
        self.setupTools()
1✔
165
        self.context = self.topLevelObject
1✔
166
        self.storedContexts = []
1✔
167
        self.doNotOutput = []
1✔
168
        self.currentMeasure = None
1✔
169
        self.addedVariants = []
1✔
170
        self.variantColors = ['blue', 'red', 'purple', 'green', 'orange', 'yellow', 'grey']
1✔
171
        self.coloredVariants = False
1✔
172
        self.variantMode = False
1✔
173
        self.LILYEXEC = None
1✔
174
        self.tempName = None
1✔
175
        self.inWord = None
1✔
176

177
    def findLilyExec(self):
1✔
178
        lpEnvironment = environLocal['lilypondPath']
1✔
179
        if lpEnvironment is not None and lpEnvironment.exists():
1✔
180
            LILYEXEC = str(lpEnvironment)  # pragma: no cover
181
        else:  # pragma: no cover
182
            platform = common.getPlatform()
183
            if platform == 'darwin':
184
                LILYEXEC = '/Applications/Lilypond.app/Contents/Resources/bin/lilypond'
185
                if not os.path.exists(LILYEXEC):
186
                    LILYEXEC = 'lilypond'
187
            elif platform == 'win' and os.path.exists('c:/Program Files (x86)'):
188
                LILYEXEC = r'c:/Program\ Files\ (x86)/lilypond/usr/bin/lilypond'
189
                if not os.path.exists(LILYEXEC) and not os.path.exists(LILYEXEC + '.exe'):
190
                    LILYEXEC = 'lilypond'
191
            elif platform == 'win':
192
                LILYEXEC = r'c:/Program\ Files/lilypond/usr/bin/lilypond'
193
                if not os.path.exists(LILYEXEC) and not os.path.exists(LILYEXEC + '.exe'):
194
                    LILYEXEC = 'lilypond'
195
            else:
196
                LILYEXEC = 'lilypond'
197

198
        self.LILYEXEC = LILYEXEC
1✔
199
        return LILYEXEC
1✔
200

201
    def setupTools(self):
1✔
202
        LILYEXEC = self.findLilyExec()
1✔
203
        command = [LILYEXEC, '--version']
1✔
204
        platform = common.getPlatform()
1✔
205
        creation_flags = subprocess.CREATE_NO_WINDOW if platform == 'win' else 0
1✔
206
        try:
1✔
207
            with subprocess.Popen(command, stdout=subprocess.PIPE,
1✔
208
                                  creationflags=creation_flags) as proc:
209
                stdout, unused = proc.communicate()
1✔
210
                stdout = stdout.decode(encoding='utf-8')
1✔
211
                versionString = stdout.split()[2]
1✔
212
                versionPieces = versionString.split('.')
1✔
213
        except OSError as exc:  # pragma: no cover
214
            raise LilyTranslateException(
215
                'Cannot find a copy of Lilypond installed on your system. '
216
                + 'Please be sure it is installed. And that your '
217
                + "environment.UserSettings()['lilypondPath'] is set to find it.") from exc
218

219
        self.majorVersion = versionPieces[0]
1✔
220
        self.minorVersion = versionPieces[1]
1✔
221

222
        self.versionString = (self.topLevelObject.backslash
1✔
223
                              + 'version '
224
                              + self.topLevelObject.quoteString(str(self.majorVersion)
225
                                                                + '.'
226
                                                                + str(self.minorVersion)))
227
        self.versionScheme = lyo.LyEmbeddedScm(self.versionString)
1✔
228
        self.headerScheme = lyo.LyEmbeddedScm(self.bookHeader)
1✔
229

230
        self.backend = 'ps'
1✔
231

232
        if int(self.majorVersion) >= 2:
1✔
233
            if int(self.minorVersion) >= 11:
1✔
234
                self.backendString = '-dbackend='
1✔
235
            else:  # pragma: no cover
236
                self.backendString = '--backend='
237
        else:  # pragma: no cover
238
            self.backendString = '--backend='
239
        # I had a note that said 2.12 and > should use
240
        #    'self.backendString = '--formats=' ' but doesn't seem true
241

242
    def newContext(self, newContext):
1✔
243
        self.storedContexts.append(self.context)
1✔
244
        self.context = newContext
1✔
245

246
    def restoreContext(self):
1✔
247
        try:
1✔
248
            self.context = self.storedContexts.pop()
1✔
249
        except IndexError:  # pragma: no cover
250
            self.context = self.topLevelObject
251

252
    # ----------- Set a complete Lilypond Tree from a music21 object ----------#
253

254
    def textFromMusic21Object(self, m21ObjectIn):
1✔
255
        r'''
256
        get a proper lilypond text file for writing from a music21 object
257

258

259
        >>> n = note.Note()
260
        >>> print(lily.translate.LilypondConverter().textFromMusic21Object(n))
261
        \version "2..."
262
        \include "lilypond-book-preamble.ly"
263
        color = #(define-music-function (parser location color) (string?) #{
264
                \once \override NoteHead.color = #(x11-color color)
265
                \once \override Stem.color = #(x11-color color)
266
                \once \override Rest.color = #(x11-color color)
267
                \once \override Beam.color = #(x11-color color)
268
             #})
269
        \header { }
270
        \score  {
271
              << \new Staff  = ... { c' 4
272
                      }
273
                >>
274
          }
275
        \paper { }
276
        ...
277
        '''
278
        self.loadFromMusic21Object(m21ObjectIn)
1✔
279
        s = str(self.topLevelObject)
1✔
280
        s = re.sub(r'\s*\n\s*\n', '\n', s).strip()
1✔
281
        return s
1✔
282

283
    def loadFromMusic21Object(self, m21ObjectIn):
1✔
284
        r'''
285
        Create a Lilypond object hierarchy in self.topLevelObject from an
286
        arbitrary music21 object.
287

288
        TODO: make lilypond automatically run makeNotation.makeTupletBrackets(s)
289
        TODO: Add tests.
290
        '''
291
        c = m21ObjectIn.classes
1✔
292
        if 'Stream' in c:
1✔
293
            if m21ObjectIn[variant.Variant]:
×
294
                # has variants. so we need to make a deepcopy
295
                m21ObjectIn = variant.makeAllVariantsReplacements(m21ObjectIn, recurse=True)
×
296
                variant.makeVariantBlocks(m21ObjectIn)
×
297

298
        if ('Stream' not in c) or ('Measure' in c) or ('Voice' in c):
1✔
299
            scoreObj = stream.Score()
1✔
300
            partObj = stream.Part()
1✔
301
            # no need for measures or voices
302
            partObj.insert(0, m21ObjectIn)
1✔
303
            scoreObj.insert(0, partObj)
1✔
304
            self.loadObjectFromScore(scoreObj, makeNotation=False)
1✔
305
        elif 'Part' in c:
×
306
            scoreObj = stream.Score()
×
307
            scoreObj.insert(0, m21ObjectIn)
×
308
            self.loadObjectFromScore(scoreObj, makeNotation=False)
×
309
        elif 'Score' in c:
×
310
            self.loadObjectFromScore(m21ObjectIn, makeNotation=False)
×
311
        elif 'Opus' in c:
×
312
            self.loadObjectFromOpus(m21ObjectIn, makeNotation=False)
×
313
        else:  # treat as part
314
            scoreObj = stream.Score()
×
315
            scoreObj.insert(0, m21ObjectIn)
×
316
            self.loadObjectFromScore(scoreObj, makeNotation=False)
×
317
            # raise LilyTranslateException('Unknown stream type %s.' % (m21ObjectIn.__class__))
318

319
    def loadObjectFromOpus(self, opusIn=None, makeNotation=True):
1✔
320
        r'''
321
        creates a filled topLevelObject (lily.lilyObjects.LyLilypondTop)
322
        whose string representation accurately reflects all the Score objects
323
        in this Opus object.
324

325

326
        >>> #_DOCS_SHOW fifeOpus = corpus.parse('miscFolk/americanfifeopus.abc')
327
        >>> #_DOCS_SHOW lpc = lily.translate.LilypondConverter()
328
        >>> #_DOCS_SHOW lpc.loadObjectFromOpus(fifeOpus, makeNotation=False)
329
        >>> #_DOCS_SHOW lpc.showPDF()
330
        '''
331
        contents = []
×
332
        lpVersionScheme = self.versionScheme
×
333
        lpHeaderScheme = self.headerScheme
×
334
        lpColorScheme = lyo.LyEmbeddedScm(self.colorDef)
×
335
        contents.append(lpVersionScheme)
×
336
        contents.append(lpHeaderScheme)
×
337
        contents.append(lpColorScheme)
×
338

339
        for thisScore in opusIn.scores:
×
340
            if makeNotation is True:
×
341
                thisScore = thisScore.makeNotation(inPlace=False)
×
342

343
            lpHeader = lyo.LyLilypondHeader()
×
344
            lpScoreBlock = self.lyScoreBlockFromScore(thisScore)
×
345
            if thisScore.metadata is not None:
×
346
                self.setHeaderFromMetadata(thisScore.metadata, lpHeader=lpHeader)
×
347

348
            contents.append(lpHeader)
×
349
            contents.append(lpScoreBlock)
×
350

351
        lpOutputDefHead = lyo.LyOutputDefHead(defType='paper')
×
352
        lpOutputDefBody = lyo.LyOutputDefBody(outputDefHead=lpOutputDefHead)
×
353
        lpOutputDef = lyo.LyOutputDef(outputDefBody=lpOutputDefBody)
×
354
        contents.append(lpOutputDef)
×
355

356
        lpLayout = lyo.LyLayout()
×
357

358
        contents.append(lpLayout)
×
359

360
        self.context.contents = contents
×
361

362
    def loadObjectFromScore(self, scoreIn=None, makeNotation=True):
1✔
363
        r'''
364

365
        creates a filled topLevelObject (lily.lilyObjects.LyLilypondTop)
366
        whose string representation accurately reflects this Score object.
367

368

369
        >>> lpc = lily.translate.LilypondConverter()
370
        >>> #_DOCS_SHOW b = corpus.parse('bach/bwv66.6')
371
        >>> b = lily.translate._getCachedCorpusFile('bach/bwv66.6') #_DOCS_HIDE
372
        >>> lpc.loadObjectFromScore(b)
373
        '''
374
        if makeNotation is True:
1✔
375
            scoreIn = scoreIn.makeNotation(inPlace=False)
1✔
376

377
        lpVersionScheme = self.versionScheme
1✔
378
        lpHeaderScheme = self.headerScheme
1✔
379
        lpColorScheme = lyo.LyEmbeddedScm(self.colorDef)
1✔
380
        lpHeader = lyo.LyLilypondHeader()
1✔
381

382
        # here's the heavy work
383
        lpScoreBlock = self.lyScoreBlockFromScore(scoreIn)
1✔
384

385
        lpOutputDefHead = lyo.LyOutputDefHead(defType='paper')
1✔
386
        lpOutputDefBody = lyo.LyOutputDefBody(outputDefHead=lpOutputDefHead)
1✔
387
        lpOutputDef = lyo.LyOutputDef(outputDefBody=lpOutputDefBody)
1✔
388
        lpLayout = lyo.LyLayout()
1✔
389
        contents = [lpVersionScheme, lpHeaderScheme, lpColorScheme,
1✔
390
                    lpHeader, lpScoreBlock, lpOutputDef, lpLayout]
391

392
        if scoreIn.metadata is not None:
1✔
393
            self.setHeaderFromMetadata(scoreIn.metadata, lpHeader=lpHeader)
1✔
394

395
        self.context.contents = contents
1✔
396

397
    # ------ return Lily objects or append to the current context -----------#
398
    def lyScoreBlockFromScore(self, scoreIn):
1✔
399

400
        lpCompositeMusic = lyo.LyCompositeMusic()
1✔
401
        self.newContext(lpCompositeMusic)
1✔
402

403
        # Also get the variants, and the total number of measures here and make start each
404
        # staff context with { \stopStaff s1*n} where n is the number of measures.
405
        if hasattr(scoreIn, 'parts') and scoreIn.iter().parts:  # or has variants
1✔
406
            if scoreIn[variant.Variant]:
1✔
407
                lpPartsAndOssiaInit = self.lyPartsAndOssiaInitFromScore(scoreIn)
×
408
                lpGroupedMusicList = self.lyGroupedMusicListFromScoreWithParts(
×
409
                    scoreIn,
410
                    scoreInit=lpPartsAndOssiaInit)
411
            else:
412
                lpGroupedMusicList = self.lyGroupedMusicListFromScoreWithParts(scoreIn)
1✔
413
            lpCompositeMusic.groupedMusicList = lpGroupedMusicList
1✔
414
        else:
415
            # treat as a part
416
            lpPrefixCompositeMusic = self.lyPrefixCompositeMusicFromStream(scoreIn)
1✔
417
            lpCompositeMusic.prefixCompositeMusic = lpPrefixCompositeMusic
1✔
418

419
        lpMusic = lyo.LyMusic(compositeMusic=lpCompositeMusic)
1✔
420
        lpScoreBody = lyo.LyScoreBody(music=lpMusic)
1✔
421
        lpScoreBlock = lyo.LyScoreBlock(scoreBody=lpScoreBody)
1✔
422
        self.restoreContext()
1✔
423

424
        return lpScoreBlock
1✔
425

426
    def lyPartsAndOssiaInitFromScore(self, scoreIn):
1✔
427
        r'''
428
        Takes in a score and returns a block that starts each part context and variant context
429
        with an identifier and {\stopStaff s1*n} (or s, whatever is needed for the duration)
430
        where n is the number of measures in the score.
431

432

433
        >>> import copy
434

435
        Set up score:
436

437
        >>> s = stream.Score()
438
        >>> p1,p2 = stream.Part(), stream.Part()
439
        >>> p1.insert(0, meter.TimeSignature('4/4'))
440
        >>> p2.insert(0, meter.TimeSignature('4/4'))
441
        >>> p1.append(variant.Variant(name='london'))
442
        >>> p2.append(variant.Variant(name='london'))
443
        >>> p1.append(variant.Variant(name='rome'))
444
        >>> p2.append(variant.Variant(name='rome'))
445
        >>> for i in range(4):
446
        ...    m = stream.Measure()
447
        ...    n = note.Note('D4', type='whole')
448
        ...    m.append(n)
449
        ...    p1.append(m)
450
        ...    p2.append(copy.deepcopy(m))
451
        >>> p1.id = 'pa'
452
        >>> p2.id = 'pb'
453
        >>> s.append(p1)
454
        >>> s.append(p2)
455

456
        Run method
457

458
        >>> lpc = lily.translate.LilypondConverter()
459
        >>> print(lpc.lyPartsAndOssiaInitFromScore(s))
460
        \new Staff  = pa { \stopStaff s1 s1 s1 s1 }
461
        \new Staff  = londonpa
462
                    \with {
463
                          \remove "Time_signature_engraver"
464
                          alignAboveContext = #"pa"
465
                          fontSize = #-3
466
                          \override StaffSymbol.staff-space = #(magstep -3)
467
                          \override StaffSymbol.thickness = #(magstep -3)
468
                          \override TupletBracket.bracket-visibility = ##f
469
                          \override TupletNumber.stencil = ##f
470
                          \override Clef.transparent = ##t
471
                          \override OctavateEight.transparent = ##t
472
                          \consists "Default_bar_line_engraver"
473
                        }
474
                 { \stopStaff s1 s1 s1 s1 }
475
        \new Staff  = romepa
476
                    \with {
477
                          \remove "Time_signature_engraver"
478
                          alignAboveContext = #"pa"
479
                          fontSize = #-3
480
                          \override StaffSymbol.staff-space = #(magstep -3)
481
                          \override StaffSymbol.thickness = #(magstep -3)
482
                          \override TupletBracket.bracket-visibility = ##f
483
                          \override TupletNumber.stencil = ##f
484
                          \override Clef.transparent = ##t
485
                          \override OctavateEight.transparent = ##t
486
                          \consists "Default_bar_line_engraver"
487
                        }
488
                 { \stopStaff s1 s1 s1 s1 }
489
        \new Staff  = pb { \stopStaff s1 s1 s1 s1 }
490
        \new Staff  = londonpb
491
                    \with {
492
                          \remove "Time_signature_engraver"
493
                          alignAboveContext = #"pb...
494
                          fontSize = #-3
495
                          \override StaffSymbol.staff-space = #(magstep -3)
496
                          \override StaffSymbol.thickness = #(magstep -3)
497
                          \override TupletBracket.bracket-visibility = ##f
498
                          \override TupletNumber.stencil = ##f
499
                          \override Clef.transparent = ##t
500
                          \override OctavateEight.transparent = ##t
501
                          \consists "Default_bar_line_engraver"
502
                        }
503
                 { \stopStaff s1 s1 s1 s1 }
504
        \new Staff  = romepb
505
                    \with {
506
                          \remove "Time_signature_engraver"
507
                          alignAboveContext = #"pb...
508
                          fontSize = #-3
509
                          \override StaffSymbol.staff-space = #(magstep -3)
510
                          \override StaffSymbol.thickness = #(magstep -3)
511
                          \override TupletBracket.bracket-visibility = ##f
512
                          \override TupletNumber.stencil = ##f
513
                          \override Clef.transparent = ##t
514
                          \override OctavateEight.transparent = ##t
515
                          \consists "Default_bar_line_engraver"
516
                        }
517
                 { \stopStaff s1 s1 s1 s1 }
518
        '''
519
        lpMusicList = lyo.LyMusicList()
1✔
520

521
        musicList = []
1✔
522
        lpMusic = r'{ \stopStaff %s}'
1✔
523

524
        for p in scoreIn.parts:
1✔
525
            partIdText = makeLettersOnlyId(p.id)
1✔
526
            partId = lyo.LyOptionalId(partIdText)
1✔
527
            spacerDuration = self.getLySpacersFromStream(p)
1✔
528
            lpPrefixCompositeMusicPart = lyo.LyPrefixCompositeMusic(type='new',
1✔
529
                                                                    optionalId=partId,
530
                                                                    simpleString='Staff',
531
                                                                    music=lpMusic % spacerDuration)
532
            musicList.append(lpPrefixCompositeMusicPart)
1✔
533

534
            variantsAddedForPart = []
1✔
535
            for v in p.getElementsByClass(variant.Variant):
1✔
536
                variantName = v.groups[0]
1✔
537
                if variantName not in variantsAddedForPart:
1✔
538
                    self.addedVariants.append(variantName)
1✔
539
                    variantsAddedForPart.append(variantName)
1✔
540
                    variantId = lyo.LyOptionalId(makeLettersOnlyId(variantName) + partIdText)
1✔
541
                    lpPrefixCompositeMusicVariant = lyo.LyPrefixCompositeMusic(
1✔
542
                        type='new',
543
                        optionalId=variantId,
544
                        simpleString='Staff',
545
                        music=lpMusic % spacerDuration
546
                    )
547

548
                    contextModList = [r'\remove "Time_signature_engraver"',
1✔
549
                                      fr'alignAboveContext = #"{partIdText}"',
550
                                      r'fontSize = #-3',
551
                                      r'\override StaffSymbol.staff-space = #(magstep -3)',
552
                                      r'\override StaffSymbol.thickness = #(magstep -3)',
553
                                      r'\override TupletBracket.bracket-visibility = ##f',
554
                                      r'\override TupletNumber.stencil = ##f',
555
                                      r'\override Clef.transparent = ##t',
556
                                      r'\override OctavateEight.transparent = ##t',
557
                                      r'\consists "Default_bar_line_engraver"',
558
                                      ]
559
                    optionalContextMod = lyo.LyContextModification(contextModList)
1✔
560
                    lpPrefixCompositeMusicVariant.optionalContextMod = optionalContextMod
1✔
561
                    musicList.append(lpPrefixCompositeMusicVariant)
1✔
562

563
        lpMusicList.contents = musicList
1✔
564

565
        return lpMusicList
1✔
566

567
    def getLySpacersFromStream(self, streamIn, measuresOnly=True):
1✔
568
        # noinspection PyShadowingNames
569
        r'''
570
        Creates a series of Spacer objects for the measures in a Stream Part.
571

572

573
        >>> m1 = stream.Measure(converter.parse('tinynotation: 3/4 a2.'))
574
        >>> m2 = stream.Measure(converter.parse('tinynotation: 3/4 b2.'))
575
        >>> m3 = stream.Measure(converter.parse('tinynotation: 4/4 a1'))
576
        >>> m4 = stream.Measure(converter.parse('tinynotation: 4/4 b1'))
577
        >>> m5 = stream.Measure(converter.parse('tinynotation: 4/4 c1'))
578
        >>> m6 = stream.Measure(converter.parse('tinynotation: 5/4 a4 b1'))
579
        >>> streamIn = stream.Stream([m1, m2, m3, m4, m5, m6])
580
        >>> lpc = lily.translate.LilypondConverter()
581
        >>> print(lpc.getLySpacersFromStream(streamIn))
582
        s2. s2. s1 s1 s1 s1 s4
583

584
        TODO: Low-priority: rare, but possible: tuplet time signatures (3/10), etc.
585
        '''
586

587
        returnString = ''
1✔
588
        # mostRecentDur = ''
589
        # recentDurCount = 0
590
        for el in streamIn:
1✔
591
            if not isinstance(el, stream.Measure):
1✔
592
                continue
1✔
593
            if el.duration.quarterLength == 0.0:
1✔
594
                continue
×
595

596
            # noinspection PyBroadException
597
            try:
1✔
598
                dur = str(self.lyMultipliedDurationFromDuration(el.duration))
1✔
599
                returnString = returnString + 's' + dur
1✔
600
            # general exception is the only way to catch str exceptions
601
            except:  # pylint: disable=bare-except
1✔
602
                for c in el.duration.components:
1✔
603
                    dur = str(self.lyMultipliedDurationFromDuration(c))
1✔
604
                    returnString = returnString + 's' + dur
1✔
605
            # if dur == mostRecentDur:
606
            #    recentDurCount += 1
607
            # else:
608
            #    mostRecentDur = dur
609
            #    recentDurCount = 0
610

611
        # if recentDurCount != 0:
612
        #    returnString = returnString + '*' + str(recentDurCount)
613

614
        return returnString
1✔
615

616
    def lyGroupedMusicListFromScoreWithParts(self, scoreIn, scoreInit=None):
1✔
617
        # noinspection PyShadowingNames,GrazieInspection
618
        r'''
619
        More complex example showing how the score can be set up with ossia parts:
620

621
        >>> lpc = lily.translate.LilypondConverter()
622
        >>> #_DOCS_SHOW b = corpus.parse('bach/bwv66.6')
623
        >>> b = lily.translate._getCachedCorpusFile('bach/bwv66.6')  #_DOCS_HIDE
624
        >>> lpPartsAndOssiaInit = lpc.lyPartsAndOssiaInitFromScore(b)
625
        >>> lpGroupedMusicList = lpc.lyGroupedMusicListFromScoreWithParts(b,
626
        ...                scoreInit=lpPartsAndOssiaInit)
627
        >>> print(lpGroupedMusicList)
628
        <BLANKLINE>
629
         << \new Staff  = Soprano { \stopStaff s4 s1 s1 s1 s1 s1 s1 s1 s1 s2. }
630
           \new Staff  = Alto { \stopStaff s4 s1 s1 s1 s1 s1 s1 s1 s1 s2. }
631
           \new Staff  = Tenor { \stopStaff s4 s1 s1 s1 s1 s1 s1 s1 s1 s2. }
632
           \new Staff  = Bass { \stopStaff s4 s1 s1 s1 s1 s1 s1 s1 s1 s2. }
633
        <BLANKLINE>
634
          \context Staff  = Soprano \with {
635
              \autoBeamOff
636
          }
637
          { \startStaff \partial 32*8
638
                \clef "treble"
639
                \key fis \minor
640
                \time 4/4
641
                \set stemRightBeamCount = #1
642
                \once \override Stem.direction = #DOWN
643
                cis'' 8 [
644
                \set stemLeftBeamCount = #1
645
                \once \override Stem.direction = #DOWN
646
                b... 8 ]
647
                \bar "|"  %{ end measure 0 %}
648
                \once \override Stem.direction = #UP
649
                a' 4
650
                \once \override Stem.direction = #DOWN
651
                b... 4
652
                \once \override Stem.direction = #DOWN
653
                cis'' 4  \fermata
654
                \once \override Stem.direction = #DOWN
655
                e'' 4
656
                \bar "|"  %{ end measure 1 %}
657
                \once \override Stem.direction = #DOWN
658
                cis'' 4
659
                ...
660
        }
661
        <BLANKLINE>
662
        <BLANKLINE>
663
        \context Staff  = Alto \with  {
664
            \autoBeamOff
665
         }
666
         { \startStaff \partial 32*8
667
            \clef "treble"...
668
            \once \override Stem.direction = #UP
669
            e' 4
670
            \bar "|"  %{ end measure 0 %}
671
            \once \override Stem.direction = #UP
672
            fis' 4
673
            \once \override Stem.direction = #UP
674
            e' 4
675
        ...
676
        }
677
        <BLANKLINE>
678
        <BLANKLINE>
679
        >>
680
        <BLANKLINE>
681
        '''
682

683
        compositeMusicList = []
1✔
684

685
        lpGroupedMusicList = lyo.LyGroupedMusicList()
1✔
686
        lpSimultaneousMusic = lyo.LySimultaneousMusic()
1✔
687
        lpMusicList = lyo.LyMusicList()
1✔
688
        lpSimultaneousMusic.musicList = lpMusicList
1✔
689
        lpGroupedMusicList.simultaneousMusic = lpSimultaneousMusic
1✔
690

691
        self.newContext(lpMusicList)
1✔
692

693
        if scoreInit is None:
1✔
694
            for p in scoreIn.parts:
1✔
695
                compositeMusicList.append(self.lyPrefixCompositeMusicFromStream(p))
1✔
696
        else:
697
            compositeMusicList.append(scoreInit)
1✔
698
            for p in scoreIn.parts:
1✔
699
                compositeMusicList.append(
1✔
700
                    self.lyPrefixCompositeMusicFromStream(p,
701
                                                          type='context',
702
                                                          beforeMatter='startStaff'))
703

704
        self.restoreContext()
1✔
705
        lpMusicList.contents = compositeMusicList
1✔
706

707
        return lpGroupedMusicList
1✔
708

709
    def lyNewLyricsFromStream(self, streamIn, streamId=None, alignment='alignBelowContext'):
1✔
710
        r'''
711
        returns a LyNewLyrics object
712

713
        This is a bit of a hack. This should be switched over to using a
714
        prefixed context thing with \new Lyric = "id" \with { } {}
715

716
        >>> s = converter.parse('tinyNotation: 4/4 c4_hel- d4_-lo r4 e4_world')
717
        >>> s.makeMeasures(inPlace=True)
718
        >>> s.id = 'helloWorld'
719

720
        >>> lpc = lily.translate.LilypondConverter()
721
        >>> lyNewLyrics = lpc.lyNewLyricsFromStream(s)
722
        >>> print(lyNewLyrics)
723
        \addlyrics { \set alignBelowContext = #"helloWorld"
724
           "hel" --
725
           "lo"__
726
           "world"
727
            }
728
        '''
729
        lyricsDict = streamIn.lyrics(skipTies=True)
1✔
730

731
        if streamId is None:
1✔
732
            streamId = makeLettersOnlyId(streamIn.id)
1✔
733

734
        streamId = '#' + lyo.LyObject().quoteString(streamId)
1✔
735

736
        lpGroupedMusicLists = []
1✔
737
        for lyricNum in sorted(lyricsDict):
1✔
738
            lyricList = []
1✔
739
            lpAlignmentProperty = lyo.LyPropertyOperation(mode='set',
1✔
740
                                                          value1=alignment,
741
                                                          value2=streamId)
742
            lyricList.append(lpAlignmentProperty)
1✔
743

744
            self.inWord = False
1✔
745
            for el in lyricsDict[lyricNum]:
1✔
746
                lpLyricElement = self.lyLyricElementFromM21Lyric(el)
1✔
747
                lyricList.append(lpLyricElement)
1✔
748

749
            self.inWord = False
1✔
750

751
            lpLyricList = lyo.LyMusicList(lyricList)
1✔
752

753
            lpSequentialMusic = lyo.LySequentialMusic(musicList=lpLyricList)
1✔
754
            lpGroupedMusicList = lyo.LyGroupedMusicList(sequentialMusic=lpSequentialMusic)
1✔
755
            lpGroupedMusicLists.append(lpGroupedMusicList)
1✔
756

757
        lpNewLyrics = lyo.LyNewLyrics(groupedMusicLists=lpGroupedMusicLists)
1✔
758

759
        return lpNewLyrics
1✔
760

761
    def lyLyricElementFromM21Lyric(self, m21Lyric):
1✔
762
        r'''
763
        Returns a :class:`~music21.lily.lilyObjects.LyLyricElement` object
764
        from a :class:`~music21.note.Lyric` object.
765

766
        Uses self.inWord to keep track of whether we're in the middle of
767
        a word.
768

769
        >>> s = converter.parse('tinyNotation: 4/4 c4_hel- d4_-lo r2 e2 f2_world')
770
        >>> s.makeMeasures(inPlace=True)
771
        >>> lyrics = s.lyrics()[1]  # get first verse (yes, 1 = first, not 0!)
772

773
        >>> lpc = lily.translate.LilypondConverter()
774
        >>> lpc.lyLyricElementFromM21Lyric(lyrics[0])
775
        <music21.lily.lilyObjects.LyLyricElement "hel" -->
776
        >>> lpc.inWord
777
        True
778
        >>> lpc.lyLyricElementFromM21Lyric(lyrics[1])
779
        <music21.lily.lilyObjects.LyLyricElement "lo"__>
780
        >>> lpc.lyLyricElementFromM21Lyric(lyrics[2])
781
        <music21.lily.lilyObjects.LyLyricElement _>
782
        >>> lpc.lyLyricElementFromM21Lyric(lyrics[3])
783
        <music21.lily.lilyObjects.LyLyricElement "world">
784
        >>> lpc.inWord
785
        False
786
        '''
787

788
        if hasattr(self, 'inWord'):
1✔
789
            inWord = self.inWord
1✔
790
        else:
791
            inWord = False
×
792

793
        el = m21Lyric
1✔
794
        if el is None and inWord:
1✔
795
            text = ' _ '
×
796
        elif el is None and inWord is False:
1✔
797
            text = ' _ '
1✔
798
        elif el.text == '':
1✔
799
            text = ' _ '
1✔
800
        elif el.text is None:
1✔
801
            text = ''
×
802
        else:
803
            text = '"' + el.text + '"'
1✔
804
            # TODO: composite
805
            if el.syllabic == 'end':
1✔
806
                text = text + '__'
1✔
807
                inWord = False
1✔
808
            elif el.syllabic in ('begin', 'middle'):
1✔
809
                text = text + ' --'
1✔
810
                inWord = True
1✔
811
            # else: pass
812

813
        self.inWord = inWord
1✔
814
        lpLyricElement = lyo.LyLyricElement(text)
1✔
815
        return lpLyricElement
1✔
816

817
    def lySequentialMusicFromStream(self, streamIn, beforeMatter=None):
1✔
818
        r'''
819
        returns a LySequentialMusic object from a stream
820

821
        >>> c = converter.parse('tinynotation: 3/4 C4 D E F2.')
822
        >>> lpc = lily.translate.LilypondConverter()
823
        >>> lySequentialMusicOut = lpc.lySequentialMusicFromStream(c)
824
        >>> lySequentialMusicOut
825
        <music21.lily.lilyObjects.LySequentialMusic { \clef "b...>
826
        >>> print(lySequentialMusicOut)
827
        { \clef "bass"
828
         \time 3/4
829
         c 4
830
         d 4
831
         e 4
832
         \bar "|"  %{ end measure 1 %}
833
         f 2.
834
         \bar "|."  %{ end measure 2 %}
835
          }
836
        <BLANKLINE>
837
        '''
838
        musicList = []
1✔
839

840
        lpMusicList = lyo.LyMusicList(contents=musicList)
1✔
841
        lpSequentialMusic = lyo.LySequentialMusic(musicList=lpMusicList,
1✔
842
                                                  beforeMatter=beforeMatter)
843
        self.newContext(lpMusicList)
1✔
844
        self.appendObjectsToContextFromStream(streamIn)
1✔
845

846
        lyObject = self.closeMeasure()
1✔
847
        if lyObject is not None:
1✔
848
            musicList.append(lyObject)
1✔
849

850
        self.restoreContext()
1✔
851
        return lpSequentialMusic
1✔
852

853
    # pylint: disable=redefined-builtin
854
    def lyPrefixCompositeMusicFromStream(
1✔
855
        self,
856
        streamIn,
857
        contextType=None,
858
        type=None,
859
        beforeMatter=None
860
    ):
861
        # noinspection PyShadowingNames
862
        r'''
863
        returns an LyPrefixCompositeMusic object from
864
        a stream (generally a part, but who knows!)
865

866
        >>> c = converter.parse('tinynotation: 3/4 C4 D E F2.')
867
        >>> c.staffLines = 4
868

869
        >>> lpc = lily.translate.LilypondConverter()
870
        >>> lyPrefixCompositeMusicOut = lpc.lyPrefixCompositeMusicFromStream(c, contextType='Staff')
871
        >>> lyPrefixCompositeMusicOut
872
        <music21.lily.lilyObjects.LyPrefixCompositeMusic \new Staff...>
873
        >>> print(lyPrefixCompositeMusicOut)
874
        \new Staff = ... \with {
875
         \override StaffSymbol.line-count = #4
876
        }
877
        { \clef "bass"
878
             \time 3/4
879
             c 4
880
             d 4
881
             e 4
882
             \bar "|"  %{ end measure 1 %}
883
             f 2.
884
             \bar "|."  %{ end measure 2 %}
885
              }
886
        <BLANKLINE>
887
        <BLANKLINE>
888
        '''
889
        compositeMusicType = type
1✔
890

891
        optionalId = None
1✔
892
        contextModList = []
1✔
893

894
        c = streamIn.classes
1✔
895
        if contextType is None:
1✔
896
            if 'Part' in c:
1✔
897
                newContext = 'Staff'
1✔
898
                optionalId = lyo.LyOptionalId(makeLettersOnlyId(streamIn.id))
1✔
899
            elif 'Voice' in c:
1✔
900
                newContext = 'Voice'
1✔
901
            else:
902
                newContext = 'Voice'
1✔
903
        else:
904
            newContext = contextType
1✔
905
            optionalId = lyo.LyOptionalId(makeLettersOnlyId(streamIn.id))
1✔
906

907
        if streamIn.streamStatus.beams is True:
1✔
908
            contextModList.append(r'\autoBeamOff ')
1✔
909

910
        if hasattr(streamIn, 'staffLines') and streamIn.staffLines != 5:
1✔
911
            contextModList.append(fr'\override StaffSymbol.line-count = #{streamIn.staffLines}')
1✔
912
            if streamIn.staffLines % 2 == 0:  # even stafflines need a change
1✔
913
                pass
1✔
914

915
        lpNewLyrics = self.lyNewLyricsFromStream(streamIn, streamId=makeLettersOnlyId(streamIn.id))
1✔
916

917
        lpSequentialMusic = self.lySequentialMusicFromStream(streamIn, beforeMatter=beforeMatter)
1✔
918
        lpGroupedMusicList = lyo.LyGroupedMusicList(sequentialMusic=lpSequentialMusic)
1✔
919
        lpCompositeMusic = lyo.LyCompositeMusic(groupedMusicList=lpGroupedMusicList,
1✔
920
                                                newLyrics=lpNewLyrics)
921
        lpMusic = lyo.LyMusic(compositeMusic=lpCompositeMusic)
1✔
922

923
        if compositeMusicType is None:
1✔
924
            compositeMusicType = 'new'
1✔
925

926
        if contextModList:
1✔
927
            contextMod = lyo.LyContextModification(contextModList)
1✔
928
        else:
929
            contextMod = None
1✔
930

931
        lpPrefixCompositeMusic = lyo.LyPrefixCompositeMusic(type=compositeMusicType,
1✔
932
                                                            optionalId=optionalId,
933
                                                            simpleString=newContext,
934
                                                            optionalContextMod=contextMod,
935
                                                            music=lpMusic)
936
        return lpPrefixCompositeMusic
1✔
937

938
    def appendObjectsToContextFromStream(self, streamObject):
1✔
939
        r'''
940
        takes a Stream and appends all the elements in it to the current
941
        context's .contents list, and deals with creating Voices in it. It also deals with
942
        variants in it.
943

944
        (should eventually replace the main Score parts finding tools)
945

946

947
        >>> lpc = lily.translate.LilypondConverter()
948
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
949
        >>> lpc.context = lpMusicList
950
        >>> lpc.context.contents
951
        []
952
        >>> c = converter.parse('tinynotation: 3/4 b4 d- e#')
953
        >>> lpc.appendObjectsToContextFromStream(c)
954
        >>> print(lpc.context.contents)
955
        [<music21.lily.lilyObjects.LyEmbeddedScm...>,
956
         <music21.lily.lilyObjects.LySimpleMusic...>,
957
         <music21.lily.lilyObjects.LySimpleMusic...>,
958
         <music21.lily.lilyObjects.LySimpleMusic...]
959
        >>> print(lpc.context)
960
        \clef "treble"
961
        \time 3/4
962
        b' 4
963
        des' 4
964
        eis' 4
965
        <BLANKLINE>
966

967

968
        >>> v1 = stream.Voice()
969
        >>> v1.append(note.Note('C5', quarterLength = 4.0))
970
        >>> v2 = stream.Voice()
971
        >>> v2.append(note.Note('C#5', quarterLength = 4.0))
972
        >>> m = stream.Measure()
973
        >>> m.insert(0, v1)
974
        >>> m.insert(0, v2)
975
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
976
        >>> lpc.context = lpMusicList
977
        >>> lpc.appendObjectsToContextFromStream(m)
978
        >>> print(lpc.context)  # internal spaces removed
979
          << \new Voice { c'' 1
980
                    \bar "|."  %{ end measure 1 %}
981
                  }
982
           \new Voice { cis'' 1
983
                  }
984
            >>
985
        '''
986
        from music21.stream.iterator import OffsetIterator
1✔
987
        for groupedElements in OffsetIterator(streamObject):
1✔
988
            # print(groupedElements)
989

990
            if len(groupedElements) == 1:  # one thing at that moment
1✔
991
                el = groupedElements[0]
1✔
992
                el.activeSite = streamObject
1✔
993
                self.appendM21ObjectToContext(el)
1✔
994
            else:  # voices or other More than one thing at once
995
                # if voices
996
                voiceList = []
1✔
997
                variantList = []
1✔
998
                otherList = []
1✔
999
                for el in groupedElements:
1✔
1000
                    if isinstance(el, stream.Voice):
1✔
1001
                        voiceList.append(el)
1✔
1002
                    elif isinstance(el, variant.Variant):
1✔
1003
                        variantList.append(el)
×
1004
                    else:
1005
                        el.activeSite = streamObject
1✔
1006
                        otherList.append(el)
1✔
1007

1008
                if variantList:
1✔
1009
                    for v in variantList:
×
1010
                        v.activeSite = streamObject
×
1011
                    self.appendContextFromVariant(variantList,
×
1012
                                                  activeSite=streamObject,
1013
                                                  coloredVariants=self.coloredVariants)
1014

1015
                if voiceList:
1✔
1016
                    musicList2 = []
1✔
1017
                    lp2GroupedMusicList = lyo.LyGroupedMusicList()
1✔
1018
                    lp2SimultaneousMusic = lyo.LySimultaneousMusic()
1✔
1019
                    lp2MusicList = lyo.LyMusicList()
1✔
1020
                    lp2SimultaneousMusic.musicList = lp2MusicList
1✔
1021
                    lp2GroupedMusicList.simultaneousMusic = lp2SimultaneousMusic
1✔
1022

1023
                    for voice in voiceList:
1✔
1024
                        if voice not in self.doNotOutput:
1✔
1025
                            lpPrefixCompositeMusic = self.lyPrefixCompositeMusicFromStream(voice)
1✔
1026
                            musicList2.append(lpPrefixCompositeMusic)
1✔
1027

1028
                    lp2MusicList.contents = musicList2
1✔
1029

1030
                    contextObject = self.context
1✔
1031
                    currentMusicList = contextObject.contents
1✔
1032
                    currentMusicList.append(lp2GroupedMusicList)
1✔
1033
                    lp2GroupedMusicList.setParent(self.context)
1✔
1034

1035
                if otherList:
1✔
1036
                    for el in otherList:
1✔
1037
                        self.appendM21ObjectToContext(el)
1✔
1038

1039
    def appendM21ObjectToContext(self, thisObject):
1✔
1040
        r'''
1041
        converts any type of object into a lilyObject of LyMusic (
1042
        LySimpleMusic, LyEmbeddedScm etc.) type
1043
        '''
1044
        if thisObject in self.doNotOutput:
1✔
1045
            return
×
1046

1047
        # treat complex duration objects as multiple objects
1048
        c = thisObject.classes
1✔
1049

1050
        if 'Stream' not in c and thisObject.duration.type == 'complex':
1✔
1051
            thisObjectSplit = thisObject.splitAtDurations()
1✔
1052
            for subComponent in thisObjectSplit:
1✔
1053
                self.appendM21ObjectToContext(subComponent)
1✔
1054
            return
1✔
1055

1056
        contextObject = self.context
1✔
1057
        if hasattr(contextObject, 'contents'):
1✔
1058
            currentMusicList = contextObject.contents
1✔
1059
        else:  # pragma: no cover
1060
            raise LilyTranslateException(
1061
                f'Cannot get a currentMusicList from contextObject {contextObject!r}')
1062

1063
        if hasattr(thisObject, 'startTransparency') and thisObject.startTransparency is True:
1✔
1064
            # old hack, replace with the better "hidden" attribute
1065
            lyScheme = lyo.LyEmbeddedScm(self.transparencyStartScheme)
×
1066
            currentMusicList.append(lyScheme)
×
1067

1068
        lyObject = None
1✔
1069
        if 'Measure' in c:
1✔
1070
            # lilypond does not put groups around measures
1071
            # it does however need barline ends
1072
            # also, if variantMode is True, the last note in each "measure" should have \noBeam
1073
            closeMeasureObj = self.closeMeasure()  # could be None
1✔
1074
            if closeMeasureObj is not None:
1✔
1075
                currentMusicList.append(closeMeasureObj)
1✔
1076
                closeMeasureObj.setParent(contextObject)
1✔
1077

1078
            padObj = self.getSchemeForPadding(thisObject)
1✔
1079
            if padObj is not None:
1✔
1080
                currentMusicList.append(padObj)
1✔
1081
                padObj.setParent(contextObject)
1✔
1082

1083
            # here we go!
1084
            self.appendObjectsToContextFromStream(thisObject)
1✔
1085
            self.currentMeasure = thisObject
1✔
1086

1087
        elif 'Stream' in c:
1✔
1088
            # try:
1089
            lyObject = self.lyPrefixCompositeMusicFromStream(thisObject)
×
1090
            currentMusicList.append(lyObject)
×
1091
            lyObject.setParent(contextObject)
×
1092
            # except AttributeError as ae:
1093
            #    raise ValueError('Cannot parse %s: %s' % (thisObject, str(ae)))
1094
        elif 'Note' in c or 'Rest' in c:
1✔
1095
            self.appendContextFromNoteOrRest(thisObject)
1✔
1096
        elif 'Chord' in c:
1✔
1097
            self.appendContextFromChord(thisObject)
×
1098
        elif 'Clef' in c:
1✔
1099
            lyObject = self.lyEmbeddedScmFromClef(thisObject)
1✔
1100
            currentMusicList.append(lyObject)
1✔
1101
            lyObject.setParent(contextObject)
1✔
1102
        elif 'KeySignature' in c:
1✔
1103
            lyObject = self.lyEmbeddedScmFromKeySignature(thisObject)
1✔
1104
            currentMusicList.append(lyObject)
1✔
1105
            lyObject.setParent(contextObject)
1✔
1106
        elif 'TimeSignature' in c and self.variantMode is False:
1✔
1107
            lyObject = self.lyEmbeddedScmFromTimeSignature(thisObject)
1✔
1108
            currentMusicList.append(lyObject)
1✔
1109
            lyObject.setParent(contextObject)
1✔
1110
        elif 'Variant' in c:
1✔
1111
            self.appendContextFromVariant(thisObject, coloredVariants=self.coloredVariants)
×
1112
        elif 'SystemLayout' in c:
1✔
1113
            lyObject = lyo.LyEmbeddedScm(r'\break')
1✔
1114
            currentMusicList.append(lyObject)
1✔
1115
            lyObject.setParent(contextObject)
1✔
1116
        elif 'PageLayout' in c:
1✔
1117
            lyObject = lyo.LyEmbeddedScm(r'\pageBreak')
×
1118
            currentMusicList.append(lyObject)
×
1119
            lyObject.setParent(contextObject)
×
1120
        else:
1121
            lyObject = None
1✔
1122

1123
        if hasattr(thisObject, 'stopTransparency') and thisObject.stopTransparency is True:
1✔
1124
            # old hack, replace with the better "hidden" attribute
1125
            lyScheme = lyo.LyEmbeddedScm(self.transparencyStopScheme)
×
1126
            currentMusicList.append(lyScheme)
×
1127

1128
    def appendContextFromNoteOrRest(self, noteOrRest):
1✔
1129
        r'''
1130
        appends lySimpleMusicFromNoteOrRest to the
1131
        current context.
1132

1133

1134
        >>> n = note.Note('C#4')
1135
        >>> lpc = lily.translate.LilypondConverter()
1136
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
1137
        >>> lpc.context = lpMusicList
1138
        >>> lpc.appendContextFromNoteOrRest(n)
1139
        >>> print(lpMusicList)
1140
        cis' 4
1141
        <BLANKLINE>
1142

1143

1144
        >>> n2 = note.Note('D#4')
1145
        >>> n2.duration.quarterLength = 1/3
1146
        >>> n2.duration.tuplets[0].type = 'start'
1147
        >>> n3 = note.Note('E4')
1148
        >>> n3.duration.quarterLength = 1/3
1149
        >>> n4 = note.Note('F4')
1150
        >>> n4.duration.quarterLength = 1/3
1151
        >>> n4.duration.tuplets[0].type = 'stop'
1152

1153
        >>> n5 = note.Note('F#4')
1154

1155
        >>> lpc.appendContextFromNoteOrRest(n2)
1156
        >>> lpc.appendContextFromNoteOrRest(n3)
1157
        >>> lpc.appendContextFromNoteOrRest(n4)
1158
        >>> lpc.appendContextFromNoteOrRest(n5)
1159

1160
        >>> print(lpc.context)
1161
        cis' 4
1162
        \times 2/3 { dis' 8
1163
           e' 8
1164
           f' 8
1165
            }
1166
        <BLANKLINE>
1167
        fis' 4
1168
        <BLANKLINE>
1169

1170
        '''
1171
        # to be removed once grace notes are supported
1172
        if noteOrRest.duration.isGrace:
1✔
1173
            return
×
1174

1175
        # commented out until complete
1176
        # if self.variantMode is True:
1177
        #     # TODO: attach \noBeam to note if it is the last note
1178
        #     if isinstance(noteOrRest, note.NotRest):
1179
        #         n = noteOrRest
1180
        #         activeSite = n.activeSite
1181
        #         offset = n.offset
1182
        #         # failed at least once
1183
        #         if offset + n.duration.quarterLength == activeSite.duration.quarterLength:
1184
        #             pass
1185

1186
        self.setContextForTupletStart(noteOrRest)
1✔
1187
        self.appendBeamCode(noteOrRest)
1✔
1188
        self.appendStemCode(noteOrRest)
1✔
1189

1190
        lpSimpleMusic = self.lySimpleMusicFromNoteOrRest(noteOrRest)
1✔
1191
        self.context.contents.append(lpSimpleMusic)
1✔
1192
        lpSimpleMusic.setParent(self.context)
1✔
1193
        self.setContextForTupletStop(noteOrRest)
1✔
1194

1195
    def appendContextFromChord(self, chord):
1✔
1196
        r'''
1197
        appends lySimpleMusicFromChord to the
1198
        current context.
1199

1200

1201
        >>> c = chord.Chord(['C4', 'E4', 'G4'])
1202
        >>> lpc = lily.translate.LilypondConverter()
1203
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
1204
        >>> lpc.context = lpMusicList
1205
        >>> lpc.appendContextFromChord(c)
1206
        >>> print(lpMusicList)
1207
        < c' e' g'  > 4
1208
        <BLANKLINE>
1209

1210

1211
        >>> c2 = chord.Chord(['D4', 'F#4', 'A4'])
1212
        >>> c2.duration.quarterLength = 1/3
1213
        >>> c2.duration.tuplets[0].type = 'start'
1214
        >>> c3 = chord.Chord(['D4', 'F4', 'G4'])
1215
        >>> c3.duration.quarterLength = 1/3
1216
        >>> c4 = chord.Chord(['C4', 'E4', 'G4', 'C5'])
1217
        >>> c4.duration.quarterLength = 1/3
1218
        >>> c4.duration.tuplets[0].type = 'stop'
1219

1220
        >>> c5 = chord.Chord(['C4', 'F4', 'A-4'])
1221

1222
        >>> lpc.appendContextFromChord(c2)
1223
        >>> lpc.appendContextFromChord(c3)
1224
        >>> lpc.appendContextFromChord(c4)
1225
        >>> lpc.appendContextFromChord(c5)
1226

1227
        >>> print(lpc.context)
1228
        < c'  e'  g'  > 4
1229
        \times 2/3 { < d'  fis'  a'  > 8
1230
           < d'  f'  g'  > 8
1231
           < c'  e'  g'  c''  > 8
1232
            }
1233
        <BLANKLINE>
1234
        < c'  f'  aes'  > 4
1235
        <BLANKLINE>
1236

1237
        '''
1238
        self.setContextForTupletStart(chord)
1✔
1239
        self.appendBeamCode(chord)
1✔
1240
        self.appendStemCode(chord)
1✔
1241

1242
        lpSimpleMusic = self.lySimpleMusicFromChord(chord)
1✔
1243
        self.context.contents.append(lpSimpleMusic)
1✔
1244
        lpSimpleMusic.setParent(self.context)
1✔
1245
        self.setContextForTupletStop(chord)
1✔
1246

1247
    def lySimpleMusicFromNoteOrRest(self, noteOrRest):
1✔
1248
        r'''
1249
        returns a lilyObjects.LySimpleMusic object for the generalNote containing this hierarchy::
1250

1251
            LyEventChord   containing
1252
            LySimpleChordElements containing
1253
            LySimpleElement containing
1254
            LyPitch  AND
1255
            LyMultipliedDuration containing:
1256

1257
                LyMultipliedDuration containing
1258
                LyStenoDuration
1259

1260
        does not check for tuplets.  That's in
1261
        appendContextFromNoteOrRest
1262

1263
        read-only property that returns a string of the lilypond representation of
1264
        a note (or via subclassing, rest or chord)
1265

1266
        >>> conv = lily.translate.LilypondConverter()
1267

1268
        >>> n0 = note.Note('D#5')
1269
        >>> n0.pitch.accidental.displayType = 'always'
1270
        >>> n0.pitch.accidental.displayStyle = 'parentheses'
1271
        >>> n0.style.color = 'blue'
1272
        >>> sm = conv.lySimpleMusicFromNoteOrRest(n0)
1273
        >>> print(sm)
1274
        \override NoteHead.color = "blue"
1275
        \override Stem.color = "blue"
1276
        dis'' ! ? 4
1277

1278
        Now make the note disappear:
1279

1280
        >>> n0.style.hideObjectOnPrint = True
1281
        >>> sm = conv.lySimpleMusicFromNoteOrRest(n0)
1282
        >>> print(sm)
1283
        s 4
1284
        '''
1285
        c = noteOrRest.classes
1✔
1286

1287
        simpleElementParts = []
1✔
1288

1289
        # https://lilypond.org/doc/v2.22/Documentation/notation/inside-the-staff#coloring-objects
1290
        if noteOrRest.hasStyleInformation:
1✔
1291
            if noteOrRest.style.color and noteOrRest.style.hideObjectOnPrint is False:
1✔
1292
                # LilyPond 2.22 (January 2021) supports hex values
1293
                noteheadColor = rf'\override NoteHead.color = "{noteOrRest.style.color}"' + '\n'
1✔
1294
                stemColor = rf'\override Stem.color = "{noteOrRest.style.color}"' + '\n'
1✔
1295
                simpleElementParts.append(noteheadColor)
1✔
1296
                simpleElementParts.append(stemColor)
1✔
1297

1298
        if 'Note' in c:
1✔
1299
            if not noteOrRest.hasStyleInformation or noteOrRest.style.hideObjectOnPrint is False:
1✔
1300
                lpPitch = self.lyPitchFromPitch(noteOrRest.pitch)
1✔
1301
                simpleElementParts.append(lpPitch)
1✔
1302
                if noteOrRest.pitch.accidental is not None:
1✔
1303
                    if noteOrRest.pitch.accidental.displayType == 'always':
1✔
1304
                        simpleElementParts.append('! ')
1✔
1305
                    if noteOrRest.pitch.accidental.displayStyle == 'parentheses':
1✔
1306
                        simpleElementParts.append('? ')
1✔
1307
            else:
1308
                simpleElementParts.append('s ')
1✔
1309
        elif 'Rest' in c:
1✔
1310
            if noteOrRest.hasStyleInformation and noteOrRest.style.hideObjectOnPrint:
1✔
1311
                simpleElementParts.append('s ')
1✔
1312
            else:
1313
                simpleElementParts.append('r ')
×
1314

1315
        lpMultipliedDuration = self.lyMultipliedDurationFromDuration(noteOrRest.duration)
1✔
1316
        simpleElementParts.append(lpMultipliedDuration)
1✔
1317

1318
        if hasattr(noteOrRest, 'beams') and noteOrRest.beams:
1✔
1319
            if noteOrRest.beams.beamsList[0].type == 'start':
1✔
1320
                simpleElementParts.append('[ ')
1✔
1321
            elif noteOrRest.beams.beamsList[0].type == 'stop':
1✔
1322
                simpleElementParts.append('] ')  # no start-stop in music21...
1✔
1323

1324
        simpleElement = lyo.LySimpleElement(parts=simpleElementParts)
1✔
1325

1326
        postEvents = self.postEventsFromObject(noteOrRest)
1✔
1327

1328
        evc = lyo.LyEventChord(simpleElement, postEvents=postEvents)
1✔
1329
        mlSM = lyo.LySimpleMusic(eventChord=evc)
1✔
1330

1331
        return mlSM
1✔
1332

1333
    def appendBeamCode(self, noteOrChord):
1✔
1334
        r'''
1335
        Adds an LyEmbeddedScm object to the context's contents if the object's has a .beams
1336
        attribute.
1337

1338
        >>> lpc = lily.translate.LilypondConverter()
1339
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
1340
        >>> lpc.context = lpMusicList
1341
        >>> lpc.context.contents
1342
        []
1343
        >>> n1 = note.Note(quarterLength=0.25)
1344
        >>> n2 = note.Note(quarterLength=0.25)
1345
        >>> n1.beams.fill(2, 'start')
1346
        >>> n2.beams.fill(2, 'stop')
1347

1348
        >>> lpc.appendBeamCode(n1)
1349
        >>> print(lpc.context.contents)
1350
        [<music21.lily.lilyObjects.LyEmbeddedScm \set stemR...>]
1351
        >>> print(lpc.context)
1352
        \set stemRightBeamCount = #2
1353

1354
        >>> lpc = lily.translate.LilypondConverter()
1355
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
1356
        >>> lpc.context = lpMusicList
1357
        >>> lpc.context.contents
1358
        []
1359
        >>> lpc.appendBeamCode(n2)
1360
        >>> print(lpc.context.contents)
1361
        [<music21.lily.lilyObjects.LyEmbeddedScm \set stemL...>]
1362
        >>> print(lpc.context)
1363
        \set stemLeftBeamCount = #2
1364

1365
        '''
1366
        leftBeams = 0
1✔
1367
        rightBeams = 0
1✔
1368
        if hasattr(noteOrChord, 'beams'):
1✔
1369
            if noteOrChord.beams is not None:
1✔
1370
                for b in noteOrChord.beams:
1✔
1371
                    if b.type == 'start':
1✔
1372
                        rightBeams += 1
1✔
1373
                    elif b.type == 'continue':
1✔
1374
                        rightBeams += 1
×
1375
                        leftBeams += 1
×
1376
                    elif b.type == 'stop':
1✔
1377
                        leftBeams += 1
1✔
1378
                    elif b.type == 'partial':
×
1379
                        if b.direction == 'left':
×
1380
                            leftBeams += 1
×
1381
                        else:  # better wrong direction than none
1382
                            rightBeams += 1
×
1383
                if leftBeams > 0:
1✔
1384
                    beamText = rf'''\set stemLeftBeamCount = #{leftBeams}'''
1✔
1385
                    lpBeamScheme = lyo.LyEmbeddedScm(beamText)
1✔
1386
                    self.context.contents.append(lpBeamScheme)
1✔
1387
                    lpBeamScheme.setParent(self.context)
1✔
1388

1389
                if rightBeams > 0:
1✔
1390
                    beamText = fr'''\set stemRightBeamCount = #{rightBeams}'''
1✔
1391
                    lpBeamScheme = lyo.LyEmbeddedScm(beamText)
1✔
1392
                    self.context.contents.append(lpBeamScheme)
1✔
1393
                    lpBeamScheme.setParent(self.context)
1✔
1394

1395
    def appendStemCode(self, noteOrChord):
1✔
1396
        r'''
1397
        Adds an LyEmbeddedScm object to the context's contents if the object's stem direction
1398
        is set (currently, only "up" and "down" are supported).
1399

1400

1401
        >>> lpc = lily.translate.LilypondConverter()
1402
        >>> lpMusicList = lily.lilyObjects.LyMusicList()
1403
        >>> lpc.context = lpMusicList
1404
        >>> lpc.context.contents
1405
        []
1406
        >>> n = note.Note()
1407
        >>> n.stemDirection = 'up'
1408
        >>> lpc.appendStemCode(n)
1409
        >>> print(lpc.context.contents)
1410
        [<music21.lily.lilyObjects.LyEmbeddedScm \once \ove...>]
1411
        >>> print(lpc.context.contents[0])
1412
        \once \override Stem.direction = #UP
1413
        '''
1414
        if hasattr(noteOrChord, 'stemDirection') and noteOrChord.stemDirection is not None:
1✔
1415
            stemDirection = noteOrChord.stemDirection.upper()
1✔
1416
            if stemDirection in ['UP', 'DOWN']:
1✔
1417
                stemFile = fr'''\once \override Stem.direction = #{stemDirection} '''
1✔
1418
                lpStemScheme = lyo.LyEmbeddedScm(stemFile)
1✔
1419
                self.context.contents.append(lpStemScheme)
1✔
1420
                lpStemScheme.setParent(self.context)
1✔
1421

1422
    def lySimpleMusicFromChord(self, chordObj):
1✔
1423
        '''
1424

1425

1426
        >>> conv = lily.translate.LilypondConverter()
1427
        >>> c1 = chord.Chord(['C#2', 'E4', 'D#5'])
1428
        >>> c1.quarterLength = 3.5
1429
        >>> c1.pitches[2].accidental.displayType = 'always'
1430
        >>> print(conv.lySimpleMusicFromChord(c1))
1431
         < cis, e' dis''  !  > 2..
1432

1433
        test hidden chord:
1434

1435
        >>> c1.style.hideObjectOnPrint = True
1436
        >>> print(conv.lySimpleMusicFromChord(c1))
1437
        s 2..
1438
        '''
1439
        self.appendBeamCode(chordObj)
1✔
1440
        if not chordObj.hasStyleInformation or chordObj.style.hideObjectOnPrint is not True:
1✔
1441

1442
            self.appendStemCode(chordObj)
1✔
1443

1444
            chordBodyElements = []
1✔
1445
            for p in chordObj.pitches:
1✔
1446
                chordBodyElementParts = []
1✔
1447
                lpPitch = self.lyPitchFromPitch(p)
1✔
1448
                chordBodyElementParts.append(lpPitch)
1✔
1449
                if p.accidental is not None:
1✔
1450
                    if p.accidental.displayType == 'always':
1✔
1451
                        chordBodyElementParts.append('! ')
1✔
1452
                    if p.accidental.displayStyle == 'parentheses':
1✔
1453
                        chordBodyElementParts.append('? ')
×
1454
                lpChordElement = lyo.LyChordBodyElement(parts=chordBodyElementParts)
1✔
1455
                chordBodyElements.append(lpChordElement)
1✔
1456
            lpChordBody = lyo.LyChordBody(chordBodyElements=chordBodyElements)
1✔
1457
        else:
1458
            lpChordBody = lyo.LyPitch('s ', '')
1✔
1459

1460
        lpMultipliedDuration = self.lyMultipliedDurationFromDuration(chordObj.duration)
1✔
1461

1462
        postEvents = self.postEventsFromObject(chordObj)
1✔
1463

1464
        lpNoteChordElement = lyo.LyNoteChordElement(chordBody=lpChordBody,
1✔
1465
                                                    optionalNoteModeDuration=lpMultipliedDuration,
1466
                                                    postEvents=postEvents)
1467
        evc = lyo.LyEventChord(noteChordElement=lpNoteChordElement)
1✔
1468
        mlSM = lyo.LySimpleMusic(eventChord=evc)
1✔
1469
        return mlSM
1✔
1470
        # TODO: Chord beaming
1471

1472
    def postEventsFromObject(self, generalNote):
1✔
1473
        r'''
1474
        attaches events that apply to notes and chords (and some other things) equally
1475
        '''
1476

1477
        postEvents = []
1✔
1478

1479
        # remove this hack once lyrics work
1480
        # if generalNote.lyric is not None:  # hack that uses markup
1481
        #    postEvents.append(r'_\markup { "' + generalNote.lyric + '" }\n ')
1482
        # consider this hack removed. Yeah!
1483

1484
        if hasattr(generalNote, 'tie') and generalNote.tie is not None:
1✔
1485
            if generalNote.tie.type != 'stop':
1✔
1486
                postEvents.append('~ ')
1✔
1487

1488
        if hasattr(generalNote, 'expressions') and generalNote.expressions:
1✔
1489
            for thisExpression in generalNote.expressions:
1✔
1490
                if 'Fermata' in thisExpression.classes:
1✔
1491
                    postEvents.append(r'\fermata ')
1✔
1492
        return postEvents
1✔
1493

1494
    def lyPitchFromPitch(self, pitch):
1✔
1495
        r'''
1496
        converts a music21.pitch.Pitch object to a lily.lilyObjects.LyPitch
1497
        object.
1498
        '''
1499

1500
        baseName = self.baseNameFromPitch(pitch)
1✔
1501
        octaveModChars = self.octaveCharactersFromPitch(pitch)
1✔
1502
        lyPitch = lyo.LyPitch(baseName, octaveModChars)
1✔
1503
        return lyPitch
1✔
1504

1505
    def baseNameFromPitch(self, pitch):
1✔
1506
        r'''
1507
        returns a string of the base name (including accidental)
1508
        for a music21 pitch
1509
        '''
1510

1511
        baseName = pitch.step.lower()
1✔
1512
        if pitch.accidental is not None:
1✔
1513
            if pitch.accidental.name in self.accidentalConvert:
1✔
1514
                baseName += self.accidentalConvert[pitch.accidental.name]
1✔
1515
        return baseName
1✔
1516

1517
    def octaveCharactersFromPitch(self, pitch):
1✔
1518
        r'''
1519
        returns a string of single-quotes or commas or '' representing
1520
        the octave of a :class:`~music21.pitch.Pitch` object
1521
        '''
1522
        implicitOctave = pitch.implicitOctave
1✔
1523
        if implicitOctave < 3:
1✔
1524
            correctedOctave = 3 - implicitOctave
1✔
1525
            octaveModChars = ',' * correctedOctave  # C2 = c,  C1 = c,,
1✔
1526
        else:
1527
            correctedOctave = implicitOctave - 3
1✔
1528
            octaveModChars = "'" * correctedOctave  # C4 = c', C5 = c''  etc.
1✔
1529
        return octaveModChars
1✔
1530

1531
    def lyMultipliedDurationFromDuration(
1✔
1532
        self,
1533
        durationObj: duration.Duration|duration.DurationTuple,
1534
    ):
1535
        r'''
1536
        take a simple Duration (that is, one with one DurationTuple)
1537
        object and return a LyMultipliedDuration object:
1538

1539
        >>> d = duration.Duration(3)
1540
        >>> lpc = lily.translate.LilypondConverter()
1541
        >>> lyMultipliedDuration = lpc.lyMultipliedDurationFromDuration(d)
1542
        >>> str(lyMultipliedDuration)
1543
        '2. '
1544

1545
        >>> str(lpc.lyMultipliedDurationFromDuration(duration.Duration(8.0)))
1546
        '\\breve '
1547
        >>> str(lpc.lyMultipliedDurationFromDuration(duration.Duration(16.0)))
1548
        '\\longa '
1549

1550
        Does not work with zero duration notes:
1551

1552
        >>> d = duration.Duration(0.0)
1553
        >>> str(lpc.lyMultipliedDurationFromDuration(d))
1554
        Traceback (most recent call last):
1555
        music21.lily.translate.LilyTranslateException: Cannot translate an object of
1556
            zero duration <music21.duration.Duration 0.0>
1557

1558

1559
        Does not work with complex durations:
1560

1561
        >>> d = duration.Duration(5.0)
1562
        >>> str(lpc.lyMultipliedDurationFromDuration(d))
1563
        Traceback (most recent call last):
1564
        music21.lily.translate.LilyTranslateException: DurationException for durationObject
1565
            <music21.duration.Duration 5.0>: Could not determine durationNumber from complex
1566

1567
        Instead, split by components:
1568

1569
        >>> components = d.components
1570
        >>> [str(lpc.lyMultipliedDurationFromDuration(c)) for c in components]
1571
        ['1 ', '4 ']
1572
        '''
1573
        number_type: float|int|str
1574
        try:
1✔
1575
            number_type = duration.convertTypeToNumber(durationObj.type)  # module call
1✔
1576
        except duration.DurationException as de:
1✔
1577
            raise LilyTranslateException(
1✔
1578
                f'DurationException for durationObject {durationObj}: {de}')
1579

1580
        if number_type == 0:
1✔
1581
            raise LilyTranslateException(
1✔
1582
                f'Cannot translate an object of zero duration {durationObj}')
1583

1584
        if number_type < 1:
1✔
1585
            if number_type == 0.5:
1✔
1586
                number_type = r'\breve'
1✔
1587
            elif number_type == 0.25:
1✔
1588
                number_type = r'\longa'
1✔
1589
            else:  # pragma no cover
1590
                raise LilyTranslateException('Cannot support durations longer than longa')
×
1591
        else:
1592
            number_type = int(number_type)
1✔
1593

1594
        try:
1✔
1595
            stenoDuration = lyo.LyStenoDuration(number_type, int(durationObj.dots))
1✔
1596
            multipliedDuration = lyo.LyMultipliedDuration(stenoDuration)
1✔
1597
        except duration.DurationException as de:
×
1598
            raise LilyTranslateException(
×
1599
                f'DurationException: Cannot translate durationObject {durationObj}: {de}')
1600
        return multipliedDuration
1✔
1601

1602
    def lyEmbeddedScmFromClef(self, clefObj):
1✔
1603
        # noinspection PyShadowingNames
1604
        r'''
1605
        converts a Clef object to a
1606
        lilyObjects.LyEmbeddedScm object
1607

1608

1609
        >>> tc = clef.TrebleClef()
1610
        >>> conv = lily.translate.LilypondConverter()
1611
        >>> lpEmbeddedScm = conv.lyEmbeddedScmFromClef(tc)
1612
        >>> print(lpEmbeddedScm)
1613
        \clef "treble"
1614

1615
        >>> t8c = clef.Treble8vbClef()
1616
        >>> lpEmbeddedScm = conv.lyEmbeddedScmFromClef(t8c)
1617
        >>> print(lpEmbeddedScm)
1618
        \clef "treble_8"
1619

1620
        '''
1621
        dictTranslate = OrderedDict([
1✔
1622
            ('Treble8vbClef', 'treble_8'),
1623
            ('TrebleClef', 'treble'),
1624
            ('BassClef', 'bass'),
1625
            ('AltoClef', 'alto'),
1626
            ('TenorClef', 'tenor'),
1627
            ('SopranoClef', 'soprano'),
1628
            ('PercussionClef', 'percussion'),
1629
        ])
1630

1631
        c = clefObj.classes
1✔
1632
        lilyName = None
1✔
1633
        for m21Class, lilyStr in dictTranslate.items():
1✔
1634
            if m21Class in c:
1✔
1635
                lilyName = lilyStr
1✔
1636
                break
1✔
1637
        else:  # pragma: no cover
1638
            environLocal.printDebug(
1639
                f'got a clef that lilypond does not know what to do with: {clefObj}')
1640
            lilyName = ''
1641

1642
        lpEmbeddedScm = lyo.LyEmbeddedScm()
1✔
1643
        clefScheme = (lpEmbeddedScm.backslash + 'clef '
1✔
1644
                      + lpEmbeddedScm.quoteString(lilyName)
1645
                      + lpEmbeddedScm.newlineIndent)
1646
        lpEmbeddedScm.content = clefScheme
1✔
1647
        return lpEmbeddedScm
1✔
1648

1649
    def lyEmbeddedScmFromKeySignature(self, keyObj):
1✔
1650
        # noinspection PyShadowingNames
1651
        r'''
1652
        converts a Key or KeySignature object
1653
        to a lilyObjects.LyEmbeddedScm object
1654

1655

1656
        >>> d = key.Key('d')
1657
        >>> conv = lily.translate.LilypondConverter()
1658
        >>> lpEmbeddedScm = conv.lyEmbeddedScmFromKeySignature(d)
1659
        >>> print(lpEmbeddedScm)
1660
        \key d \minor
1661

1662
        Major is assumed:
1663

1664
        >>> fSharp = key.KeySignature(6)
1665
        >>> print(conv.lyEmbeddedScmFromKeySignature(fSharp))
1666
        \key fis \major
1667

1668
        '''
1669
        if not isinstance(keyObj, key.Key):
1✔
1670
            keyObj = keyObj.asKey('major')
1✔
1671

1672
        p = keyObj.tonic
1✔
1673
        m = keyObj.mode
1✔
1674

1675
        pn = self.baseNameFromPitch(p)
1✔
1676

1677
        lpEmbeddedScm = lyo.LyEmbeddedScm()
1✔
1678
        keyScheme = (lpEmbeddedScm.backslash
1✔
1679
                     + 'key ' + pn
1680
                     + ' '
1681
                     + lpEmbeddedScm.backslash + m
1682
                     + ' '
1683
                     + lpEmbeddedScm.newlineIndent)
1684
        lpEmbeddedScm.content = keyScheme
1✔
1685
        return lpEmbeddedScm
1✔
1686

1687
    def lyEmbeddedScmFromTimeSignature(self, ts):
1✔
1688
        # noinspection PyShadowingNames
1689
        r'''
1690
        convert a :class:`~music21.meter.TimeSignature` object
1691
        to a lilyObjects.LyEmbeddedScm object
1692

1693

1694
        >>> ts = meter.TimeSignature('3/4')
1695
        >>> conv = lily.translate.LilypondConverter()
1696
        >>> print(conv.lyEmbeddedScmFromTimeSignature(ts))
1697
        \time 3/4
1698
        '''
1699
        lpEmbeddedScm = lyo.LyEmbeddedScm()
1✔
1700
        keyScheme = lpEmbeddedScm.backslash + 'time ' + ts.ratioString + lpEmbeddedScm.newlineIndent
1✔
1701
        lpEmbeddedScm.content = keyScheme
1✔
1702
        return lpEmbeddedScm
1✔
1703

1704
    def setContextForTupletStart(self, inObj):
1✔
1705
        r'''
1706
        if the inObj has tuplets then we set a new context
1707
        for the tuplets and anything up till a tuplet stop.
1708

1709
        Note that a broken tuplet (à la Michael Gordon)
1710
        will not work.
1711

1712
        If there are no tuplets, this routine does
1713
        nothing.  If there are tuplets, and they have type "start", then
1714
        it returns an lpMusicList object, which is the new context
1715

1716
        For now, no support for nested tuplets.  They're an
1717
        easy extension, but there's too much
1718
        else that is missing to do it now.
1719
        '''
1720
        if not inObj.duration.tuplets:
1✔
1721
            return None
1✔
1722
        elif inObj.duration.tuplets[0].type == 'start':
1✔
1723
            numerator = str(int(inObj.duration.tuplets[0].tupletNormal[0]))
1✔
1724
            denominator = str(int(inObj.duration.tuplets[0].tupletActual[0]))
1✔
1725
            lpMusicList = self.setContextForTimeFraction(numerator, denominator)
1✔
1726
            return lpMusicList
1✔
1727
        else:
1728
            return None
1✔
1729

1730
    def setContextForTimeFraction(self, numerator, denominator):
1✔
1731
        r'''
1732
        Explicitly starts a new context for scaled music (tuplets, etc.)
1733
        for the given numerator and denominator (either an int or a string or unicode)
1734

1735
        Returns an lpMusicList object contained in an lpSequentialMusic object
1736
        in an lpPrefixCompositeMusic object which sets the times object to a particular
1737
        fraction.
1738

1739

1740
        >>> lpc = lily.translate.LilypondConverter()
1741
        >>> lpc.context
1742
        <music21.lily.lilyObjects.LyLilypondTop>
1743
        >>> lyTop = lpc.context
1744
        >>> lyoMusicList = lpc.setContextForTimeFraction(5, 4)
1745
        >>> lyoMusicList
1746
        <music21.lily.lilyObjects.LyMusicList>
1747
        >>> lpc.context
1748
        <music21.lily.lilyObjects.LyMusicList>
1749
        >>> lpc.context is lyoMusicList
1750
        True
1751
        >>> lpc.context.getParent()
1752
        <music21.lily.lilyObjects.LySequentialMusic {  }>
1753
        >>> lpc.context.getParent().getParent()
1754
        <music21.lily.lilyObjects.LyPrefixCompositeMusic \times 5/4...>
1755
        >>> lpc.context.getParent().getParent().fraction
1756
        '5/4'
1757
        >>> lpc.context.getParent().getParent().type
1758
        'times'
1759
        >>> lpc.context.getParent().getParent().getParent()
1760
        <music21.lily.lilyObjects.LyLilypondTop \times 5/4...>
1761
        >>> lpc.context.getParent().getParent().getParent() is lyTop
1762
        True
1763
        '''
1764
        fraction = str(numerator) + '/' + str(denominator)
1✔
1765
        lpMusicList = lyo.LyMusicList()
1✔
1766
        lpSequentialMusic = lyo.LySequentialMusic(musicList=lpMusicList)
1✔
1767
        # technically needed, but we can speed things up
1768
        # lpGroupedMusicList = lyo.LyGroupedMusicList(sequentialMusic=lpSequentialMusic)
1769
        # lpCompositeMusic = lyo.LyCompositeMusic(groupedMusicList=lpGroupedMusicList)
1770
        # lpMusic = lyo.LyMusic(compositeMusic=lpCompositeMusic)
1771
        lpPrefixCompositeMusic = lyo.LyPrefixCompositeMusic(type='times',
1✔
1772
                                                            fraction=fraction,
1773
                                                            music=lpSequentialMusic)
1774
        currentContents = self.context.contents
1✔
1775
        if currentContents is None:  # pragma: no cover
1776
            raise LilyTranslateException(
1777
                f'Cannot find contents for self.context: {self.context!r} ')
1778

1779
        currentContents.append(lpPrefixCompositeMusic)
1✔
1780
        lpPrefixCompositeMusic.setParent(self.context)
1✔
1781
        self.newContext(lpMusicList)
1✔
1782
        return lpMusicList
1✔
1783

1784
    def setContextForTupletStop(self, inObj):
1✔
1785
        r'''
1786
        Reverse of setContextForTupletStart
1787
        '''
1788
        if not inObj.duration.tuplets:
1✔
1789
            return
1✔
1790
        elif inObj.duration.tuplets[0].type == 'stop':
1✔
1791
            self.restoreContext()
1✔
1792
        else:
1793
            return None
1✔
1794

1795
    def appendContextFromVariant(self, variantObjectOrList, activeSite=None, coloredVariants=False):
1✔
1796
        r'''
1797
        Create a new context from the variant object or a list of variants and append.
1798
        '''
1799
        musicList = []
×
1800
        longestReplacedElements = []
×
1801

1802
        if isinstance(variantObjectOrList, variant.Variant):
×
1803
            variantObject = variantObjectOrList
×
1804
            replacedElements = variantObject.replacedElements(activeSite)
×
1805
            lpPrefixCompositeMusicVariant = self.lyPrefixCompositeMusicFromVariant(
×
1806
                variantObject, replacedElements, coloredVariants=coloredVariants
1807
            )
1808
            lpSequentialMusicStandard = self.lySequentialMusicFromStream(replacedElements)
×
1809
            musicList.append(lpPrefixCompositeMusicVariant)
×
1810
            musicList.append(lpSequentialMusicStandard)
×
1811

1812
        elif isinstance(variantObjectOrList, list):
×
1813
            longestReplacementLength = -1
×
1814
            variantDict = {}
×
1815
            for variantObject in variantObjectOrList:
×
1816
                if variantObject.groups:
×
1817
                    variantName = variantObject.groups[0]
×
1818
                else:
1819
                    variantName = 'variant'
×
1820
                if variantName in variantDict:
×
1821
                    variantDict[variantName].append(variantObject)
×
1822
                else:
1823
                    variantDict[variantName] = [variantObject]
×
1824

1825
            for variant_key in variantDict:
×
1826
                variantList = variantDict[variant_key]
×
1827
                if len(variantList) == 1:
×
1828
                    variantObject = variantList[0]
×
1829
                    replacedElements = variantObject.replacedElements(activeSite)
×
1830
                    lpPrefixCompositeMusicVariant = self.lyPrefixCompositeMusicFromVariant(
×
1831
                        variantObject, replacedElements, coloredVariants=coloredVariants)
1832
                    musicList.append(lpPrefixCompositeMusicVariant)
×
1833
                else:
1834
                    varTuple = self.lyPrefixCompositeMusicFromRelatedVariants(
×
1835
                        variantList, activeSite=activeSite, coloredVariants=coloredVariants)
1836
                    lpPrefixCompositeMusicVariant, replacedElements = varTuple
×
1837
                    musicList.append(lpPrefixCompositeMusicVariant)
×
1838

1839
                if longestReplacementLength < replacedElements.duration.quarterLength:
×
1840
                    longestReplacementLength = replacedElements.duration.quarterLength
×
1841
                    longestReplacedElements = replacedElements
×
1842

1843
            lpSequentialMusicStandard = self.lySequentialMusicFromStream(longestReplacedElements)
×
1844

1845
            musicList.append(lpSequentialMusicStandard)
×
1846
            for el in longestReplacedElements:
×
1847
                self.doNotOutput.append(el)
×
1848

1849
        lp2MusicList = lyo.LyMusicList()
×
1850
        lp2MusicList.contents = musicList
×
1851
        lp2SimultaneousMusic = lyo.LySimultaneousMusic()
×
1852
        lp2SimultaneousMusic.musicList = lp2MusicList
×
1853
        lp2GroupedMusicList = lyo.LyGroupedMusicList()
×
1854
        lp2GroupedMusicList.simultaneousMusic = lp2SimultaneousMusic
×
1855

1856
        contextObject = self.context
×
1857
        currentMusicList = contextObject.contents
×
1858
        currentMusicList.append(lp2GroupedMusicList)
×
1859
        lp2GroupedMusicList.setParent(self.context)
×
1860

1861
    def lyPrefixCompositeMusicFromRelatedVariants(self,
1✔
1862
                                                  variantList,
1863
                                                  activeSite=None,
1864
                                                  coloredVariants=False):
1865
        # noinspection PyShadowingNames
1866
        r'''
1867

1868

1869
        >>> s1 = converter.parse('tinynotation: 4/4 a4 a a a  a1')
1870
        >>> s2 = converter.parse('tinynotation: 4/4 b4 b b b')
1871
        >>> s3 = converter.parse('tinynotation: 4/4 c4 c c c')
1872
        >>> s4 = converter.parse('tinynotation: 4/4 d4 d d d')
1873
        >>> s5 = converter.parse('tinynotation: 4/4 e4 e e e  f f f f  g g g g  a a a a  b b b b')
1874

1875
        >>> for s in [ s1, s2, s3, s4, s5]:
1876
        ...     s.makeMeasures(inPlace=True)
1877

1878
        >>> activeSite = stream.Part(s5.elements)
1879

1880
        >>> v1 = variant.Variant()
1881
        >>> for el in s1:
1882
        ...     v1.append(el)
1883
        >>> v1.replacementDuration = 4.0
1884

1885
        >>> v2 = variant.Variant()
1886
        >>> sp2 = note.Rest()
1887
        >>> sp2.style.hideObjectOnPrint = True
1888
        >>> sp2.duration.quarterLength = 4.0
1889
        >>> v2.replacementDuration = 4.0
1890
        >>> v2.append(sp2)
1891
        >>> for el in s2:
1892
        ...     v2.append(el)
1893

1894
        >>> v3 = variant.Variant()
1895
        >>> sp3 = note.Rest()
1896
        >>> sp3.style.hideObjectOnPrint = True
1897
        >>> sp3.duration.quarterLength = 8.0
1898
        >>> v3.replacementDuration = 4.0
1899
        >>> v3.append(sp3)
1900
        >>> for el in s3:
1901
        ...     v3.append(el)
1902

1903
        >>> v4 = variant.Variant()
1904
        >>> sp4 = note.Rest()
1905
        >>> sp4.style.hideObjectOnPrint = True
1906
        >>> sp4.duration.quarterLength = 16.0
1907
        >>> v4.replacementDuration = 4.0
1908
        >>> v4.append(sp4)
1909
        >>> for el in s4:
1910
        ...     v4.append(el)
1911

1912
        >>> variantList = [v4, v1, v3, v2]
1913
        >>> for v in variantList :
1914
        ...     v.groups = ['london']
1915
        ...     activeSite.insert(0.0, v)
1916

1917

1918
        >>> lpc = lily.translate.LilypondConverter()
1919

1920
        >>> print(lpc.lyPrefixCompositeMusicFromRelatedVariants(variantList,
1921
        ...                activeSite=activeSite)[0])
1922
        \new Staff  = london... { { \times 1/2 {\startStaff \clef "treble"
1923
              a' 4
1924
              a' 4
1925
              a' 4
1926
              a' 4
1927
              \clef "treble"
1928
              | %{ end measure 1 %}
1929
              a' 1
1930
              | %{ end measure 2 %}
1931
               \stopStaff}
1932
               }
1933
        <BLANKLINE>
1934
          {\startStaff \clef "treble"
1935
            b... 4
1936
            b... 4
1937
            b... 4
1938
            b... 4
1939
            | %{ end measure 1 %}
1940
             \stopStaff}
1941
        <BLANKLINE>
1942
          {\startStaff \clef "treble"
1943
            c' 4
1944
            c' 4
1945
            c' 4
1946
            c' 4
1947
            | %{ end measure 1 %}
1948
             \stopStaff}
1949
        <BLANKLINE>
1950
          s 1
1951
          {\startStaff \clef "treble"
1952
            d' 4
1953
            d' 4
1954
            d' 4
1955
            d' 4
1956
            | %{ end measure 1 %}
1957
             \stopStaff}
1958
        <BLANKLINE>
1959
           }
1960
        <BLANKLINE>
1961

1962
        '''
1963

1964
        # Order List
1965

1966
        def findOffsetOfFirstNonSpacerElement(inputStream):
1✔
1967
            for el in inputStream:
1✔
1968
                if isinstance(el, note.Rest) and el.style.hideObjectOnPrint:
1✔
1969
                    pass
1✔
1970
                else:
1971
                    return inputStream.elementOffset(el)
1✔
1972

1973
        variantList.sort(key=lambda vv: findOffsetOfFirstNonSpacerElement(vv._stream))
1✔
1974

1975
        # Stuff that can be done on the first element only (clef, new/old, id, color)
1976
        replacedElements = variantList[0].replacedElements(activeSite)
1✔
1977
        re0 = replacedElements[0]
1✔
1978
        replacedElementsClef = re0.clef or re0.getContextByClass(clef.Clef)
1✔
1979

1980
        variantContainerStream = variantList[0].getContextByClass(stream.Part)
1✔
1981
        if variantContainerStream is None:
1✔
1982
            variantContainerStream = variantList[0].getContextByClass('Stream')
×
1983

1984
        variantList[0].insert(0.0, replacedElementsClef)
1✔
1985
        variantName = variantList[0].groups[0]
1✔
1986
        if variantName in self.addedVariants:
1✔
1987
            newVariant = False
×
1988
        else:
1989
            self.addedVariants.append(variantName)
1✔
1990
            newVariant = True
1✔
1991

1992
        containerId = makeLettersOnlyId(variantContainerStream.id)
1✔
1993
        variantId = lyo.LyOptionalId(makeLettersOnlyId(variantName) + containerId)
1✔
1994

1995
        if coloredVariants is True:
1✔
1996
            color = self.variantColors[self.addedVariants.index(variantName) % 6]
×
1997
        else:
1998
            color = None
1✔
1999

2000
        #######################
2001

2002
        musicList = []
1✔
2003
        highestOffsetSoFar = 0.0
1✔
2004
        longestVariant = None
1✔
2005

2006
        self.variantMode = True
1✔
2007

2008
        for v in variantList:
1✔
2009
            # For each variant in the list, we make a lilypond representation of the
2010
            # spacer between this variant and the previous if it is non-zero and append it
2011
            # Then we strip off the spacer and make a lilypond representation of the variant
2012
            # with the appropriate tupletting if any and append that.
2013
            # At the end we make a new lilypond context for it and return it.
2014

2015
            firstOffset = findOffsetOfFirstNonSpacerElement(v._stream)
1✔
2016

2017
            if firstOffset < highestOffsetSoFar:
1✔
2018
                raise LilyTranslateException('Should not have overlapping variants.')
×
2019

2020
            spacerDuration = firstOffset - highestOffsetSoFar
1✔
2021
            highestOffsetSoFar = v.replacementDuration + firstOffset
1✔
2022

2023
            # make spacer with spacerDuration and append
2024
            if spacerDuration > 0.0:
1✔
2025
                spacer = note.Rest()
1✔
2026
                spacer.style.hideObjectOnPrint = True
1✔
2027
                spacer.duration.quarterLength = spacerDuration
1✔
2028
                # noinspection PyTypeChecker
2029
                lySpacer = self.lySimpleMusicFromNoteOrRest(spacer)
1✔
2030
                musicList.append(lySpacer)
1✔
2031

2032
            if coloredVariants is True:
1✔
2033
                for n in v._stream.recurse().notesAndRests:
×
2034
                    n.style.color = color  # make thing (with or without fraction)
×
2035

2036
            # Strip off spacer
2037
            endOffset = v.containedHighestTime
1✔
2038
            vStripped = variant.Variant(v._stream.getElementsByOffset(firstOffset,
1✔
2039
                                                                      offsetEnd=endOffset))
2040
            vStripped.replacementDuration = v.replacementDuration
1✔
2041

2042
            replacedElementsLength = vStripped.replacementDuration
1✔
2043
            variantLength = vStripped.containedHighestTime - firstOffset
1✔
2044

2045
            if variantLength != replacedElementsLength:
1✔
2046
                numerator, denominator = common.decimalToTuplet(
1✔
2047
                    replacedElementsLength / variantLength)
2048
                fraction = str(numerator) + '/' + str(denominator)
1✔
2049
                lpOssiaMusicVariantPreFraction = self.lyOssiaMusicFromVariant(vStripped)
1✔
2050
                lpVariantTuplet = lyo.LyPrefixCompositeMusic(type='times',
1✔
2051
                                                             fraction=fraction,
2052
                                                             music=lpOssiaMusicVariantPreFraction)
2053

2054
                lpOssiaMusicVariant = lyo.LySequentialMusic(musicList=lpVariantTuplet)
1✔
2055
            else:
2056
                lpOssiaMusicVariant = self.lyOssiaMusicFromVariant(vStripped)
1✔
2057

2058
            musicList.append(lpOssiaMusicVariant)
1✔
2059

2060
            longestVariant = v
1✔
2061

2062
        # The last variant in the iteration should have the highestOffsetSoFar,
2063
        # so it has the appropriate replacementElements to return can compare with the rest in
2064
        # appendContextFromVariant.
2065

2066
        replacedElements = longestVariant.replacedElements(activeSite, includeSpacers=True)
1✔
2067

2068
        lpMusicList = lyo.LyMusicList(musicList)
1✔
2069
        lpInternalSequentialMusic = lyo.LySequentialMusic(musicList=lpMusicList)
1✔
2070

2071
        compositeMusicType = 'new' if newVariant else 'context'
1✔
2072
        lpPrefixCompositeMusicVariant = lyo.LyPrefixCompositeMusic(
1✔
2073
            type=compositeMusicType,
2074
            optionalId=variantId,
2075
            simpleString='Staff',
2076
            music=lpInternalSequentialMusic)
2077

2078
        self.variantMode = False
1✔
2079

2080
        return lpPrefixCompositeMusicVariant, replacedElements
1✔
2081

2082
    def lyPrefixCompositeMusicFromVariant(self,
1✔
2083
                                          variantObject,
2084
                                          replacedElements,
2085
                                          coloredVariants=False):
2086
        # noinspection PyShadowingNames
2087
        r'''
2088

2089
        >>> pStream = converter.parse('tinynotation: 4/4 a4 b c d   e4 f g a')
2090
        >>> pStream.makeMeasures(inPlace=True)
2091
        >>> p = stream.Part(pStream.elements)
2092
        >>> p.id = 'p1'
2093
        >>> vStream = converter.parse('tinynotation: 4/4 a4. b8 c4 d')
2094
        >>> vStream.makeMeasures(inPlace=True)
2095
        >>> v = variant.Variant(vStream.elements)
2096
        >>> v.groups = ['london']
2097
        >>> p.insert(0.0, v)
2098
        >>> lpc = lily.translate.LilypondConverter()
2099
        >>> replacedElements = v.replacedElements()
2100
        >>> lpPrefixCompositeMusicVariant = lpc.lyPrefixCompositeMusicFromVariant(v,
2101
        ...                                                            replacedElements)
2102
        >>> print(lpPrefixCompositeMusicVariant)  # ellipses are for non-byte fix-ups
2103
        \new Staff  = londonpx { {\startStaff \clef "treble"
2104
            a' 4.
2105
            b...
2106
            c' 4
2107
            d' 4
2108
            | %{ end measure 1 %}
2109
             \stopStaff}
2110
           }
2111

2112
        >>> replacedElements.show('text')
2113
        {0.0} <music21.stream.Measure 1 offset=0.0>
2114
            {0.0} <music21.clef.TrebleClef>
2115
            {0.0} <music21.meter.TimeSignature 4/4>
2116
            {0.0} <music21.note.Note A>
2117
            {1.0} <music21.note.Note B>
2118
            {2.0} <music21.note.Note C>
2119
            {3.0} <music21.note.Note D>
2120

2121
        >>> print(lpc.addedVariants)
2122
        ['london']
2123

2124
        '''
2125
        replacedElementsClef = replacedElements[0].getContextByClass(clef.Clef)
1✔
2126

2127
        variantContainerStream = variantObject.getContextByClass(stream.Part)
1✔
2128
        if variantContainerStream is None:
1✔
2129
            variantContainerStream = variantObject.getContextByClass('Stream')
×
2130

2131
        if replacedElementsClef is not None:
1✔
2132
            if replacedElementsClef not in variantObject.elements:
×
2133
                variantObject.insert(0, replacedElementsClef)
×
2134

2135
        if variantObject.groups:
1✔
2136
            variantName = variantObject.groups[0]
1✔
2137
        else:
2138
            variantName = 'variant'
×
2139
        if variantName in self.addedVariants:
1✔
2140
            newVariant = False
×
2141
        else:
2142
            self.addedVariants.append(variantName)
1✔
2143
            newVariant = True
1✔
2144

2145
        containerId = makeLettersOnlyId(variantContainerStream.id)
1✔
2146
        variantId = lyo.LyOptionalId(makeLettersOnlyId(variantName) + containerId)
1✔
2147

2148
        if coloredVariants is True:
1✔
2149
            color = self.variantColors[self.addedVariants.index(variantName) % 6]
×
2150
            for n in variantObject._stream.recurse().notesAndRests:
×
2151
                n.style.color = color
×
2152

2153
        musicList = []
1✔
2154

2155
        varFilter = [r for r in variantObject.getElementsByClass(note.Rest)
1✔
2156
                     if r.style.hideObjectOnPrint]
2157

2158
        if varFilter:
1✔
2159
            spacer = varFilter[0]
×
2160
            spacerDur = spacer.duration.quarterLength
×
2161
            if spacer.duration.quarterLength > 0.0:
×
2162
                lySpacer = self.lySimpleMusicFromNoteOrRest(spacer)
×
2163
                musicList.append(lySpacer)
×
2164
            variantObject.remove(spacer)
×
2165
        else:
2166
            spacerDur = 0.0
1✔
2167

2168
        lpOssiaMusicVariant = self.lyOssiaMusicFromVariant(variantObject)
1✔
2169

2170
        replacedElementsLength = variantObject.replacementDuration
1✔
2171
        variantLength = variantObject.containedHighestTime - spacerDur
1✔
2172

2173
        self.variantMode = True
1✔
2174
        if variantLength != replacedElementsLength:
1✔
2175
            numerator, denominator = common.decimalToTuplet(replacedElementsLength / variantLength)
×
2176
            fraction = str(numerator) + '/' + str(denominator)
×
2177
            lpVariantTuplet = lyo.LyPrefixCompositeMusic(type='times',
×
2178
                                                         fraction=fraction,
2179
                                                         music=lpOssiaMusicVariant)
2180
            lpInternalSequentialMusic = lyo.LySequentialMusic(musicList=lpVariantTuplet)
×
2181
            musicList.append(lpInternalSequentialMusic)
×
2182
        else:
2183
            musicList.append(lpOssiaMusicVariant)
1✔
2184

2185
        lpMusicList = lyo.LyMusicList(musicList)
1✔
2186
        lpOssiaMusicVariantWithSpacer = lyo.LySequentialMusic(musicList=lpMusicList)
1✔
2187

2188
        if newVariant is True:
1✔
2189
            lpPrefixCompositeMusicVariant = lyo.LyPrefixCompositeMusic(
1✔
2190
                type='new',
2191
                optionalId=variantId,
2192
                simpleString='Staff',
2193
                music=lpOssiaMusicVariantWithSpacer)
2194
        else:
2195
            lpPrefixCompositeMusicVariant = lyo.LyPrefixCompositeMusic(
×
2196
                type='context',
2197
                optionalId=variantId,
2198
                simpleString='Staff',
2199
                music=lpOssiaMusicVariantWithSpacer)
2200

2201
        # optionalContextMod = r'''
2202
        # \with {
2203
        #      \remove "Time_signature_engraver"
2204
        #      alignAboveContext = #"%s"
2205
        #      fontSize = #-3
2206
        #      \override StaffSymbol.staff-space = #(magstep -3)
2207
        #      \override StaffSymbol.thickness = #(magstep -3)
2208
        #      \override TupletBracket.bracket-visibility = ##f
2209
        #      \override TupletNumber.stencil = ##f
2210
        #      \override Clef.transparent = ##t
2211
        #    }
2212
        # ''' % containerId #\override BarLine.transparent = ##t
2213
        # # is the best way of fixing the #barlines that I have come up with.
2214
        # lpPrefixCompositeMusicVariant.optionalContextMod = optionalContextMod
2215

2216
        self.variantMode = False
1✔
2217

2218
        return lpPrefixCompositeMusicVariant
1✔
2219

2220
        # musicList2 = []
2221
        # musicList2.append(lpPrefixCompositeMusicVariant)
2222
        # musicList2.append(lpSequentialMusicStandard )
2223
        #
2224
        # lp2MusicList = lyo.LyMusicList()
2225
        # lp2MusicList.contents = musicList2
2226
        # lp2SimultaneousMusic = lyo.LySimultaneousMusic()
2227
        # lp2SimultaneousMusic.musicList = lp2MusicList
2228
        # lp2GroupedMusicList = lyo.LyGroupedMusicList()
2229
        # lp2GroupedMusicList.simultaneousMusic = lp2SimultaneousMusic
2230
        #
2231
        # contextObject = self.context
2232
        # currentMusicList = contextObject.contents
2233
        # currentMusicList.append(lp2GroupedMusicList)
2234
        # lp2GroupedMusicList.setParent(self.context)
2235

2236
    def lyOssiaMusicFromVariant(self, variantIn):
1✔
2237
        r'''
2238
        returns a LyOssiaMusic object from a stream
2239

2240

2241
        >>> c = converter.parse('tinynotation: 3/4 C4 D E F2.')
2242
        >>> v = variant.Variant(c.elements)
2243
        >>> lpc = lily.translate.LilypondConverter()
2244
        >>> lySequentialMusicOut = lpc.lySequentialMusicFromStream(v)
2245
        >>> lySequentialMusicOut
2246
        <music21.lily.lilyObjects.LySequentialMusic { \clef "b...>
2247
        >>> print(lySequentialMusicOut)
2248
        { \clef "bass"
2249
         \time 3/4
2250
         c 4
2251
         d 4
2252
         e 4
2253
         \bar "|"  %{ end measure 1 %}
2254
         f 2.
2255
         \bar "|."  %{ end measure 2 %}
2256
          }
2257
        <BLANKLINE>
2258
        '''
2259
        musicList = []
1✔
2260

2261
        lpMusicList = lyo.LyMusicList(contents=musicList)
1✔
2262
        lpOssiaMusic = lyo.LyOssiaMusic(musicList=lpMusicList)
1✔
2263
        self.newContext(lpMusicList)
1✔
2264

2265
        self.variantMode = True
1✔
2266
        self.appendObjectsToContextFromStream(variantIn._stream)
1✔
2267

2268
        lyObject = self.closeMeasure()
1✔
2269
        if lyObject is not None:
1✔
2270
            musicList.append(lyObject)
1✔
2271

2272
        self.restoreContext()
1✔
2273

2274
        self.variantMode = False
1✔
2275

2276
        return lpOssiaMusic
1✔
2277

2278
    def setHeaderFromMetadata(self, metadataObject=None, lpHeader=None):
1✔
2279
        # noinspection PyShadowingNames
2280
        r'''
2281
        Returns a lilypond.lilyObjects.LyLilypondHeader object
2282
        set with data from the metadata object
2283

2284
        >>> md = metadata.Metadata()
2285
        >>> md.title = 'My Title'
2286
        >>> md.alternativeTitle = 'My "sub"-title'
2287

2288
        >>> lpc = lily.translate.LilypondConverter()
2289
        >>> lpHeader = lpc.setHeaderFromMetadata(md)
2290
        >>> print(lpHeader)
2291
        \header { title = "My Title"
2292
        subtitle = "My \"sub\"-title"
2293
        }
2294
        '''
2295

2296
        if lpHeader is None:
1✔
2297
            lpHeader = lyo.LyLilypondHeader()
1✔
2298

2299
        if lpHeader.lilypondHeaderBody is None:
1✔
2300
            lpHeaderBody = lyo.LyLilypondHeaderBody()
1✔
2301
            lpHeader.lilypondHeaderBody = lpHeaderBody
1✔
2302
        else:
2303
            lpHeaderBody = lpHeader.lilypondHeaderBody
×
2304

2305
        lpHeaderBodyAssignments = lpHeaderBody.assignments
1✔
2306

2307
        if metadataObject is not None:
1✔
2308
            title = metadataObject.bestTitle
1✔
2309
            if title:
1✔
2310
                lyTitleAssignment = lyo.LyAssignment(assignmentId='title',
1✔
2311
                                                     identifierInit=lyo.LyIdentifierInit(
2312
                                                         string=title)
2313
                                                     )
2314
                lpHeaderBodyAssignments.append(lyTitleAssignment)
1✔
2315
                lyTitleAssignment.setParent(lpHeaderBody)
1✔
2316

2317
            subtitle = metadataObject.alternativeTitle
1✔
2318
            if subtitle:
1✔
2319
                lySubtitleAssignment = lyo.LyAssignment(assignmentId='subtitle',
1✔
2320
                                                        identifierInit=lyo.LyIdentifierInit(
2321
                                                            string=subtitle)
2322
                                                        )
2323
                lpHeaderBodyAssignments.append(lySubtitleAssignment)
1✔
2324
                lySubtitleAssignment.setParent(lpHeaderBody)
1✔
2325

2326
        lpHeaderBody.assignments = lpHeaderBodyAssignments
1✔
2327
        return lpHeader
1✔
2328

2329
    def closeMeasure(self, barChecksOnly=False):
1✔
2330
        # noinspection PyShadowingNames
2331
        r'''
2332
        return a LyObject or None for the end of the previous Measure
2333

2334
        uses self.currentMeasure
2335

2336
        >>> lpc = lily.translate.LilypondConverter()
2337
        >>> m = stream.Measure()
2338
        >>> m.number = 2
2339
        >>> m.rightBarline = 'double'
2340
        >>> lpc.currentMeasure = m
2341
        >>> lyObj = lpc.closeMeasure()
2342
        >>> lpc.currentMeasure is None
2343
        True
2344
        >>> print(lyObj)
2345
        \bar "||"  %{ end measure 2 %}
2346
        '''
2347
        m = self.currentMeasure
1✔
2348
        self.currentMeasure = None
1✔
2349
        if m is None:
1✔
2350
            return None
1✔
2351
        # if m.rightBarline is None:
2352
        #    return None
2353
        # elif m.rightBarline.type == 'regular':
2354
        #    return None
2355

2356
        if self.variantMode is True:
1✔
2357
            barChecksOnly = True
1✔
2358

2359
        lpBarline = lyo.LyEmbeddedScm()
1✔
2360

2361
        if barChecksOnly is True:
1✔
2362
            barString = '|'
1✔
2363
        elif m.rightBarline is None:
1✔
2364
            barString = lpBarline.backslash + 'bar ' + lpBarline.quoteString('|')
1✔
2365
        else:
2366
            barString = lpBarline.backslash + 'bar ' + lpBarline.quoteString(
1✔
2367
                self.barlineDict[m.rightBarline.type])
2368

2369
        if m.number is not None:
1✔
2370
            barString += lpBarline.comment(f'end measure {m.number}')
1✔
2371

2372
        lpBarline.content = barString
1✔
2373
        return lpBarline
1✔
2374

2375
    def getSchemeForPadding(self, measureObject):
1✔
2376
        r'''
2377
        lilypond partial durations are very strange and are really of
2378
        type LyMultipliedDuration.  You notate how many
2379
        notes are left in the measure, for a quarter note, write "4"
2380
        for an eighth, write "8", but for 3 eighths, write "8*3" !
2381
        so we will measure in 32nd notes always. It won't work for tuplets
2382
        of course.
2383

2384
        Returns a scheme object or None if not needed.
2385

2386
        >>> m = stream.Measure()
2387
        >>> m.append(meter.TimeSignature('3/4'))
2388
        >>> m.paddingLeft = 2.0
2389
        >>> lpc = lily.translate.LilypondConverter()
2390
        >>> outScheme = lpc.getSchemeForPadding(m)
2391
        >>> print(outScheme)
2392
        \partial 32*8
2393
        '''
2394
        pL = measureObject.paddingLeft
1✔
2395
        if pL == 0:
1✔
2396
            return None
1✔
2397
        measureTimeSignatures = measureObject.getTimeSignatures()
1✔
2398
        if not measureTimeSignatures:
1✔
2399
            barLength = 4.0
×
2400
        else:
2401
            ts = measureTimeSignatures[0]
1✔
2402
            barLength = ts.barDuration.quarterLength
1✔
2403
        remainingQL = barLength - pL
1✔
2404
        if remainingQL <= 0:
1✔
2405
            raise LilyTranslateException('your first pickup measure is non-existent!')
×
2406
        remaining32s = int(remainingQL * 8)
1✔
2407
        lyObject = lyo.LyEmbeddedScm()
1✔
2408
        schemeStr = lyObject.backslash + 'partial 32*' + str(remaining32s) + ' '
1✔
2409
        lyObject.content = schemeStr
1✔
2410
        return lyObject
1✔
2411

2412
    # -------------display and converter routines ---------------------#
2413
    def writeLyFile(self, ext='', fp=None):
1✔
2414
        r'''
2415
        writes the contents of the self.topLevelObject to a file.
2416

2417
        The extension should be ly.  If fp is None then a named temporary
2418
        file is created by environment.getTempFile.
2419

2420
        '''
2421
        tloOut = str(self.topLevelObject)
×
2422
        if fp is None:
×
2423
            fp = environLocal.getTempFile(ext, returnPathlib=False)
×
2424

2425
        self.tempName = pathlib.Path(fp)
×
2426

2427
        with self.tempName.open('w', encoding='utf-8') as f:
×
2428
            f.write(tloOut)
×
2429

2430
        return self.tempName
×
2431

2432
    # noinspection PyShadowingBuiltins
2433
    def runThroughLily(self, format=None,
1✔
2434
                       backend=None, fileName=None, skipWriting=False):
2435
        r'''
2436
        creates a .ly file from self.topLevelObject via .writeLyFile
2437
        then runs the file through Lilypond.
2438

2439
        Returns the full path of the file produced by lilypond including the format extension.
2440

2441
        If skipWriting is True and a fileName is given then it will run
2442
        that file through lilypond instead
2443

2444
        '''
2445
        LILYEXEC = self.findLilyExec()
×
2446
        if fileName is None:
×
2447
            fileName = self.writeLyFile(ext='ly')
×
2448
        else:
2449
            if skipWriting is False:
×
2450
                fileName = self.writeLyFile(ext='ly', fp=fileName)
×
2451

2452
        lilyCommand = '"' + LILYEXEC + '" '
×
2453
        if format is not None:
×
2454
            lilyCommand += '-f ' + format + ' '
×
2455
        if backend is not None:
×
2456
            lilyCommand += self.backendString + backend + ' '
×
2457

NEW
2458
        lilyCommand += '-o ' + str(fileName) + ' ' + str(fileName)
×
UNCOV
2459
        os.system(lilyCommand)
×
2460

2461
        try:
×
2462
            os.remove(str(fileName) + '.eps')
×
2463
        except OSError:
×
2464
            pass
×
2465
        fileForm = str(fileName) + '.' + format
×
2466
        if not os.path.exists(fileForm):
×
2467
            # cannot find full path; try current directory
2468
            fileEnd = os.path.basename(fileForm)
×
2469
            if not os.path.exists(fileEnd):
×
2470
                raise LilyTranslateException('cannot find ' + str(fileEnd)
×
2471
                                             + ' or the full path ' + str(fileForm)
2472
                                             + ' original file was ' + str(fileName))
2473
            fileForm = fileEnd
×
2474
        return pathlib.Path(fileForm)
×
2475

2476
    def createPDF(self, fileName=None):
1✔
2477
        r'''
2478
        create a PDF file from self.topLevelObject and return the filepath of the file.
2479

2480
        most users will just call stream.write('lily.pdf') on a stream.
2481
        '''
2482
        self.headerScheme.content = ''  # clear header
×
2483
        lilyFile = self.runThroughLily(backend='ps', format='pdf', fileName=fileName)
×
2484
        return lilyFile
×
2485

2486
    def showPDF(self):
1✔
2487
        r'''
2488
        create an SVG file from self.topLevelObject, show it with your pdf reader
2489
        (often Adobe Acrobat/Adobe Reader or Apple Preview)
2490
        and return the filepath of the file.
2491

2492
        most users will just call stream.Stream.show('lily.pdf') on a stream.
2493
        '''
2494
        lF = self.createPDF()
×
2495
        if not lF.exists():  # pragma: no cover
2496
            raise RuntimeError('Something went wrong with PDF Creation')
2497

2498
        if os.name == 'nt':
×
2499
            command = f'start /wait {str(lF)} && del /f {str(lF)}'
×
2500
        elif sys.platform == 'darwin':
×
2501
            command = f'open {str(lF)}'
×
2502
        else:
2503
            command = ''
×
2504
        os.system(command)
×
2505

2506
    def createPNG(self, fileName=None):
1✔
2507
        r'''
2508
        create a PNG file from self.topLevelObject and return the filepath of the file.
2509

2510
        most users will just call stream.write('lily.png') on a stream.
2511

2512
        if PIL is installed then a small white border is created around the score
2513
        '''
2514
        lilyFile = self.runThroughLily(backend='eps', format='png', fileName=fileName)
×
2515
        if noPIL is False:
×
2516
            # noinspection PyPackageRequirements
2517
            from PIL import Image, ImageOps  # type: ignore
×
2518
            # noinspection PyBroadException
2519
            try:
×
2520
                lilyImage = Image.open(str(lilyFile))
×
2521
                lilyImage2 = ImageOps.expand(lilyImage, 10, 'white')  # type: ignore
×
2522
                lilyImage2.save(str(lilyFile))
×
2523
            except Exception:  # pylint: disable=broad-exception-caught
×
2524
                pass  # no big deal probably
×
2525
        return lilyFile
×
2526

2527
    def showPNG(self):
1✔
2528
        r'''
2529
        Take the object, run it through LilyPond, and then show it as a PNG file.
2530
        On Windows, the PNG file will not be deleted, so you  will need to clean out
2531
        TEMP every once in a while.
2532

2533
        Most users will just want to call stream.Stream.show('lily.png') instead.
2534
        '''
2535
        try:
×
2536
            lilyFile = self.createPNG()
×
2537
        except LilyTranslateException as e:
×
2538
            raise LilyTranslateException('Problems creating PNG file: (' + str(e) + ')')
×
2539
        # self.showImageDirect(lilyFile)
2540
        return SubConverter().launch(lilyFile, fmt='png')
×
2541

2542
    def createSVG(self, fileName=None):
1✔
2543
        r'''
2544
        create an SVG file from self.topLevelObject and return the filepath of the file.
2545

2546
        most users will just call stream.Stream.write('lily.svg') on a stream.
2547
        '''
2548
        self.headerScheme.content = ''  # clear header
×
2549
        lilyFile = self.runThroughLily(format='svg', backend='svg', fileName=fileName)
×
2550
        return lilyFile
×
2551

2552
    def showSVG(self, fileName=None):
1✔
2553
        r'''
2554
        create an SVG file from self.topLevelObject, show it with your
2555
        svg reader (often Internet Explorer on PC)
2556
        and return the filepath of the file.
2557

2558
        most users will just call stream.Stream.show('lily.png') on a stream.
2559
        '''
2560
        lilyFile = self.createSVG(fileName)
×
2561
        return SubConverter().launch(lilyFile, fmt='svg')
×
2562

2563

2564
class LilyTranslateException(exceptions21.Music21Exception):
1✔
2565
    pass
1✔
2566

2567

2568
class Test(unittest.TestCase):
1✔
2569
    pass
1✔
2570

2571
    def testExplicitConvertChorale(self):
1✔
2572
        lpc = LilypondConverter()
1✔
2573
        b = _getCachedCorpusFile('bach/bwv66.6')
1✔
2574
        lpc.loadObjectFromScore(b, makeNotation=False)
1✔
2575
        # print(lpc.topLevelObject)
2576

2577
    def testComplexDuration(self):
1✔
2578
        from music21 import meter
1✔
2579
        s = stream.Stream()
1✔
2580
        n1 = note.Note('C')  # test no octave also!
1✔
2581
        n1.duration.quarterLength = 2.5  # BUG 2.3333333333 doesn't work right
1✔
2582
        self.assertEqual(n1.duration.type, 'complex')
1✔
2583
        n2 = note.Note('D4')
1✔
2584
        n2.duration.quarterLength = 1.5
1✔
2585
        s.append(meter.TimeSignature('4/4'))
1✔
2586
        s.append(n1)
1✔
2587
        s.append(n2)
1✔
2588
        # s.show('text')
2589
        lpc = LilypondConverter()
1✔
2590
        lpc.loadObjectFromScore(s)
1✔
2591
        # print(lpc.topLevelObject)
2592
        # lpc.showPNG()
2593
        # s.show('lily.png')
2594

2595
    def testCompositeLyrics(self):
1✔
2596
        s = corpus.parse('theoryExercises/checker_demo.xml')
1✔
2597
        lpc = LilypondConverter()
1✔
2598
        # previously this choked where .text is None on Lyric object
2599
        lpc.loadObjectFromScore(s)
1✔
2600

2601
    def testColors(self):
1✔
2602
        # pylint: disable=implicit-str-concat
2603
        red_note = note.Note()
1✔
2604
        red_note.style.color = '#FF0000'
1✔
2605
        sm = LilypondConverter().lySimpleMusicFromNoteOrRest(red_note)
1✔
2606
        self.assertEqual(
1✔
2607
            sm.stringOutput(),
2608
            r'\override NoteHead.color = "#FF0000"' '\n'
2609
            r'\override Stem.color = "#FF0000"' '\n'
2610
            "c' 4  "
2611
        )
2612

2613
        dark_green_note = note.Note()
1✔
2614
        dark_green_note.style.color = 'darkgreen'
1✔
2615
        sm = LilypondConverter().lySimpleMusicFromNoteOrRest(dark_green_note)
1✔
2616
        self.assertEqual(
1✔
2617
            sm.stringOutput(),
2618
            r'\override NoteHead.color = "darkgreen"' '\n'
2619
            r'\override Stem.color = "darkgreen"' '\n'
2620
            "c' 4  "
2621
        )
2622

2623
class TestExternal(unittest.TestCase):
1✔
2624
    show = True
1✔
2625

2626
    def xtestConvertNote(self):
2627
        n = note.Note('C5')
2628
        if self.show:
2629
            n.show('lily.png')
2630

2631
    def xtestConvertChorale(self):
2632
        b = _getCachedCorpusFile('bach/bwv66.6')
2633
        for n in b.flatten():
2634
            n.beams = None
2635
        if self.show:
2636
            b.parts[0].show('lily.svg')
2637

2638
    def xtestSlowConvertOpus(self):
2639
        fifeOpus = corpus.parse('miscFolk/americanfifeopus.abc')
2640
        if self.show:
2641
            fifeOpus.show('lily.png')
2642

2643
    def xtestBreve(self):
2644
        from music21 import meter
2645
        n = note.Note('C5')
2646
        n.duration.quarterLength = 8.0
2647
        m = stream.Measure()
2648
        m.append(meter.TimeSignature('8/4'))
2649
        m.append(n)
2650
        p = stream.Part()
2651
        p.append(m)
2652
        s = stream.Score()
2653
        s.append(p)
2654
        if self.show:
2655
            s.show('lily.png')
2656

2657
    def testStaffLines(self):
1✔
2658
        s = stream.Score()
1✔
2659
        p = stream.Part()
1✔
2660
        p.append(note.Note('B4', type='whole'))
1✔
2661
        p.staffLines = 1
1✔
2662
        s.insert(0, p)
1✔
2663
        p2 = stream.Part()
1✔
2664
        p2.append(note.Note('B4', type='whole'))
1✔
2665
        p2.staffLines = 7
1✔
2666
        s.insert(0, p2)
1✔
2667
        if self.show:
2668
            s.show('lily.png')
2669

2670

2671
# ------------------------------------------------------------------------------
2672
if __name__ == '__main__':
2673
    import music21
2674
    music21.mainTest(Test)  # , TestExternal)
2675
    # music21.mainTest(TestExternal, 'noDocTest')
2676

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc