Skip to content

Commit ea48f35

Browse files
committed
Merge branch 'master' into pr/1242
2 parents c62eb62 + 83b7020 commit ea48f35

28 files changed

Lines changed: 1024 additions & 121 deletions

.pylintrc

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ disable=
6262
fixme, # obviously known
6363
superfluous-parens, # nope -- if they make things clearer...
6464
too-many-statements, # someday
65-
no-member, # important, but too many false positives
6665
too-many-arguments, # definitely! but takes too long to get a fix now...
6766
too-many-public-methods, # maybe...
6867
too-many-branches, # yes, someday
@@ -273,11 +272,7 @@ ignored-modules=
273272

274273
# List of classes names for which member attributes should not be checked
275274
# (useful for classes with attributes dynamically set).
276-
ignored-classes=SQLObject
277-
278-
# When zope mode is activated, add a predefined set of Zope acquired attributes
279-
# to generated-members.
280-
# zope=no
275+
ignored-classes=StreamCore
281276

282277
# List of members which are set dynamically and missed by pylint inference
283278
# system, and so shouldn't trigger E0201 when accessed. Python regular

music21/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ def contextSites(
19101910
callerFirst = self
19111911
if self.isStream and self not in memo:
19121912
streamSelf = t.cast('music21.stream.Stream', self)
1913-
recursionType = streamSelf.recursionType
1913+
recursionType = streamSelf.recursionType # pylint: disable=no-member
19141914
environLocal.printDebug(
19151915
f'Caller first is {callerFirst} with offsetAppend {offsetAppend}')
19161916
if returnSortTuples:

music21/chord/__init__.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def __deepcopy__(self: _ChordBaseType, memo=None) -> _ChordBaseType:
177177
# after copying, if a Volume exists, it is linked to the old object
178178
# look at _volume so as not to create object if not already there
179179
# noinspection PyProtectedMember
180-
for d in new._notes:
180+
for d in new._notes: # pylint: disable=no-member
181181
# if .volume is called, a new Volume obj will be created
182182
if d.hasVolumeInformation():
183183
d.volume.client = new # update with new instance
@@ -379,6 +379,9 @@ def remove(self, removeItem):
379379
except ValueError:
380380
raise ValueError('Chord.remove(x), x not in chord')
381381

382+
@property
383+
def notes(self) -> t.Tuple[note.NotRest, ...]:
384+
return ()
382385

383386
@property
384387
def tie(self):
@@ -5182,7 +5185,7 @@ def multisetCardinality(self):
51825185
return len(self.pitchClasses)
51835186

51845187
@property
5185-
def notes(self):
5188+
def notes(self) -> t.Tuple[note.Note, ...]:
51865189
'''
51875190
Return a tuple (immutable) of the notes contained in the chord.
51885191
@@ -5244,7 +5247,7 @@ def notes(self):
52445247
return tuple(self._notes)
52455248

52465249
@notes.setter
5247-
def notes(self, newNotes):
5250+
def notes(self, newNotes: t.Iterable[note.Note]) -> None:
52485251
'''
52495252
sets notes to an iterable of Note objects
52505253
'''
@@ -5805,9 +5808,9 @@ def scaleDegrees(self):
58055808
'''
58065809
from music21 import scale
58075810
# roman numerals have this built in as the key attribute
5808-
if hasattr(self, 'key') and self.key is not None:
5811+
if hasattr(self, 'key') and self.key is not None: # pylint: disable=no-member
58095812
# Key is a subclass of scale.DiatonicScale
5810-
sc = self.key
5813+
sc = self.key # pylint: disable=no-member
58115814
else:
58125815
sc = self.getContextByClass(scale.Scale, sortByCreationTime=True)
58135816
if sc is None:

music21/clef.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,9 @@ def getStemDirectionForPitches(
247247
relevantPitches = pitchList
248248

249249
differenceSum = 0
250+
# pylint: disable-next=no-member
250251
if isinstance(self, (PercussionClef, PitchClef)) and self.lowestLine is not None:
251-
midLine = self.lowestLine + 4
252+
midLine = self.lowestLine + 4 # pylint: disable=no-member
252253
else:
253254
midLine = 35 # assume TrebleClef-like.
254255

music21/converter/subConverters.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ def show(self, obj, fmt, app=None, subformats=None, **keywords): # pragma: no c
357357
subformats = ['vexflow']
358358

359359
if subformats and subformats[0] == 'vexflow':
360-
return self.vfshow(obj)
360+
raise NotImplementedError
361+
# return self.vfshow(obj)
361362
# subformats = ['lilypond', 'png']
362363
if subformats:
363364
helperFormat = subformats[0]
@@ -1340,7 +1341,7 @@ def parseData(self, strData, number=None):
13401341
from music21.capella import fromCapellaXML
13411342
ci = fromCapellaXML.CapellaImporter()
13421343
ci.parseXMLText(strData)
1343-
scoreObj = ci.systemScoreFromScore(self.mainDom.documentElement)
1344+
scoreObj = ci.systemScoreFromScore(ci.mainDom.documentElement)
13441345
partScore = ci.partScoreFromSystemScore(scoreObj)
13451346
self.stream = partScore
13461347

music21/expressions.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
# ------------------------------------------------------------------------------
14301517
class Test(unittest.TestCase):
14311518

music21/features/outputFormats.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ def getHeaderLines(self):
2525
'''
2626
pass # define in subclass
2727

28+
def getString(self, includeClassLabel=True, includeId=True, lineBreak=None):
29+
pass # define in subclass
30+
2831
def write(self, fp=None, includeClassLabel=True, includeId=True):
2932
'''
3033
Write the file. If not file path is given, a temporary file will be written.

music21/freezeThaw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ def open(self, fp, zipType=None):
927927
try:
928928
storage = pickle.loads(uncompressed)
929929
except AttributeError as e:
930-
common.restoreWindowsAfterUnpickling()
930+
common.restorePathClassesAfterUnpickling()
931931
raise FreezeThawException(
932932
f'Problem in decoding: {e}'
933933
) from e

music21/graph/plot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
from music21 import environment
5151
environLocal = environment.Environment('graph.plot')
5252

53+
# Graph uses setattr, which PyLint can't infer from currently
54+
# https://github.com/PyCQA/pylint/issues/2878
55+
# pylint: disable=no-member
56+
5357

5458
# ------------------------------------------------------------------------------
5559
# graphing utilities that operate on streams

music21/humdrum/questions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def xtest015(self):
270270
if thisInt.name != "P1":
271271
intervals2[thisInt.name] += 1
272272

273-
for key in intervals2.sort(key='simpleName'):
273+
for key in intervals2.keys().sort(key='simpleName'):
274274
print(key, intervals2[key])
275275

276276
def test016(self):

0 commit comments

Comments
 (0)