@@ -1426,6 +1426,93 @@ def numberOfMarks(self, num):
14261426 raise TremoloException ('Number of marks must be a number from 0 to 8' ) from ve
14271427
14281428
1429+ class ArpeggioMark (Expression ):
1430+ '''
1431+ ArpeggioMark must be applied to a Chord (not to a single Note).
1432+
1433+ The parameter arpeggioType can be 'normal' (a squiggly line), 'up' (a squiggly line
1434+ with an up arrow), 'down' (a squiggly line with a down arrow), or 'non-arpeggio' (a
1435+ bracket instead of a squiggly line, used to indicate a non-arpeggiated chord
1436+ intervening in a sequence of arpeggiated ones).
1437+
1438+ >>> am = expressions.ArpeggioMark('normal')
1439+ >>> am.type
1440+ 'normal'
1441+
1442+ >>> am = expressions.ArpeggioMark('down')
1443+ >>> am.type
1444+ 'down'
1445+ '''
1446+ def __init__ (self , arpeggioType : t .Optional [str ] = None ):
1447+ super ().__init__ ()
1448+ if arpeggioType is None :
1449+ arpeggioType = 'normal'
1450+ if arpeggioType not in ('normal' , 'up' , 'down' , 'non-arpeggio' ):
1451+ raise ValueError (
1452+ 'Arpeggio type must be "normal", "up", "down", or "non-arpeggio"'
1453+ )
1454+ self .type = arpeggioType
1455+
1456+
1457+ class ArpeggioMarkSpanner (spanner .Spanner ):
1458+ '''
1459+ ArpeggioMarkSpanner is a multi-staff or multi-voice (i.e. multi-chord) arpeggio.
1460+ The spanner should contain all the simultaneous Chords that are to be
1461+ arpeggiated together. If there is only one arpeggiated note in a particular staff
1462+ or voice (i.e. the rest are in other staves/voices), then in that case only the
1463+ spanner can contain a Note. Do not ever put a Note that is within a Chord into a
1464+ spanner; put the Chord in instead. And do not ever put an ArpeggioMark in a note
1465+ or chord's .expressions.
1466+
1467+ The parameter arpeggioType can be 'normal' (a squiggly line), 'up' (a squiggly line
1468+ with an up arrow), 'down' (a squiggly line with a down arrow), or 'non-arpeggio' (a
1469+ bracket instead of a squiggly line, used to indicate a non-arpeggiated multi-chord
1470+ intervening in a sequence of arpeggiated ones).
1471+
1472+ >>> ams = expressions.ArpeggioMarkSpanner(arpeggioType='non-arpeggio')
1473+ >>> c1 = chord.Chord('C3 E3 G3')
1474+ >>> c2 = chord.Chord('C4 E4 G4')
1475+ >>> ams.addSpannedElements([c1, c2])
1476+ >>> ams.type
1477+ 'non-arpeggio'
1478+ >>> ams
1479+ <music21.expressions.ArpeggioMarkSpanner
1480+ <music21.chord.Chord C3 E3 G3><music21.chord.Chord C4 E4 G4>>
1481+ '''
1482+ def __init__ (self , * arguments , ** keywords ):
1483+ super ().__init__ (* arguments , ** keywords )
1484+ arpeggioType : t .Optional [str ] = keywords .get ('arpeggioType' , None )
1485+ if arpeggioType is None :
1486+ arpeggioType = 'normal'
1487+ if arpeggioType not in ('normal' , 'up' , 'down' , 'non-arpeggio' ):
1488+ raise ValueError (
1489+ 'Arpeggio type must be "normal", "up", "down", or "non-arpeggio"'
1490+ )
1491+ self .type = arpeggioType
1492+
1493+ def noteExtremes (self ) -> t .Tuple [t .Optional ['music21.note.Note' ],
1494+ t .Optional ['music21.note.Note' ]]:
1495+ '''
1496+ Return the lowest and highest note spanned by the element,
1497+ extracting them from Chords if need be.
1498+
1499+ >>> ch = chord.Chord(['C4', 'E4', 'G4'])
1500+ >>> n = note.Note('C#3')
1501+ >>> nonArp = expressions.ArpeggioMarkSpanner([ch, n])
1502+ >>> nonArp.noteExtremes()
1503+ (<music21.note.Note C#>, <music21.note.Note G>)
1504+ '''
1505+ from music21 import chord
1506+ from music21 import note
1507+ notes = []
1508+ for n_or_ch in self :
1509+ if isinstance (n_or_ch , note .Note ):
1510+ notes .append (n_or_ch )
1511+ elif isinstance (n_or_ch , chord .Chord ):
1512+ notes .extend (n_or_ch .notes )
1513+ return (min (notes ), max (notes ))
1514+
1515+
14291516# ------------------------------------------------------------------------------
14301517class Test (unittest .TestCase ):
14311518
0 commit comments