Repository: cuthbertLab/music21
Pull request #1334 (2022-07-02 19:30:36)

Fix a bug in musicxml/partStaffExporter.py where a clef-octave-change…
… can fail to be exported. Fixes issue #1333.


Patch:
diff --git a/.gitignore b/.gitignore
index 1d8fd0293..ece5f8677 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,7 +40,6 @@ develop-eggs
 lib
 lib64
 __pycache__
-venv
 
 # Installer logs
 pip-log.txt
diff --git a/.pylintrc b/.pylintrc
index 8bda4d809..f3df3c455 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -7,6 +7,9 @@
 # pygtk.require().
 # init-hook=
 
+# Profiled execution.
+profile=no
+
 # Add files or directories to the blacklist. They should be base names, not
 # paths.
 ignore=CVS
@@ -68,12 +71,16 @@ disable=
     too-many-branches,  # yes, someday
     too-many-locals,  # no
     too-many-lines,  # yes, someday
+    bad-whitespace,  # maybe later, but "bad" isn't something I necessarily agree with
+    bad-continuation,  # never remove -- this is a good thing many times.
     too-many-return-statements,  # we'll see
     too-many-instance-attributes,  # maybe later
     invalid-name,  # these are good music21 names; fix the regexp instead...
+    no-self-use,  # maybe later
     too-few-public-methods,  # never remove or set to 1
     trailing-whitespace,  # should ignore blank lines with tabs
     missing-docstring,  # gets too many well-documented properties
+    star-args,  # no problem with them...
     protected-access,  # this is an important one, but see below...
     unused-argument,
     import-self,  # fix is either to get rid of it or move away many tests...
@@ -98,8 +105,6 @@ disable=
     not-callable,  # false positives, for instance on x.next()
     raise-missing-from,  # want to do this eventually, but adding 1000 msgs not helpful
     consider-using-f-string,  # future?
-    unnecessary-lambda-assignment,  # opinionated
-    consider-using-generator,  # generators are less performant for small container sizes, like most of ours
 
 #       'protected-access', # this is an important one, but for now we do a lot of
 #                           # x = copy.deepcopy(self); x._volume = ... which is not a problem...
@@ -113,6 +118,11 @@ disable=
 output-format=text
 msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}"
 
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
 # Tells whether to display a full report or only the messages
 reports=no
 
@@ -137,6 +147,9 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
 # Required attributes for module, separated by a comma
 # required-attributes=
 
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,input
+
 # Good variable names which should always be accepted, separated by a comma
 good-names=i,j,k,ex,Run,_
 
@@ -153,33 +166,63 @@ include-naming-hint=no
 # Regular expression matching correct function names
 function-rgx=[a-z_][A-Za-z0-9_]{2,30}$
 
+# Naming hint for function names
+function-name-hint=[a-z_][A-Za-z0-9_]{2,30}$
+
 # Regular expression matching correct variable names
 variable-rgx=[a-z_][A-Za-z0-9_]{2,30}$
 
+# Naming hint for variable names
+variable-name-hint=[a-z_][A-Za-z0-9_]{2,30}$
+
 # Regular expression matching correct constant names
 const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
 
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
 # Regular expression matching correct attribute names
 attr-rgx=[a-z_][A-Za-z0-9_]{2,30}$
 
+# Naming hint for attribute names
+attr-name-hint=[a-z_][A-Za-z0-9_]{2,30}$
+
 # Regular expression matching correct argument names
 argument-rgx=[a-z_][A-Za-z0-9_]{2,30}$
 
+# Naming hint for argument names
+argument-name-hint=[a-z_][A-Za-z0-9_]{2,30}$
+
 # Regular expression matching correct class attribute names
 class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
 
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
 # Regular expression matching correct inline iteration names
 inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
 
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
 # Regular expression matching correct class names
 class-rgx=[A-Z_][a-zA-Z0-9]+$
 
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
 # Regular expression matching correct module names
 module-rgx=(([a-z_][a-zA-Z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
 
+# Naming hint for module names
+module-name-hint=(([a-z_][a-zA-Z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
 # Regular expression matching correct method names
 method-rgx=[a-z_][a-zA-Z0-9_]{2,30}$
 
+# Naming hint for method names
+method-name-hint=[a-z_][a-zA-Z0-9_]{2,30}$
+
 # Regular expression which should only match function or class names that do
 # not require a docstring.
 no-docstring-rgx=__.*__
@@ -201,6 +244,9 @@ ignore-long-lines=^\s*(# )?<?https?://\S+>?$|converter\.parse
 # else.
 single-line-if-stmt=no
 
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=trailing-comma,dict-separator
+
 # Maximum number of lines in a module
 max-module-lines=1000
 
diff --git a/documentation/source/usersGuide/usersGuide_26_iterators.ipynb b/documentation/source/usersGuide/usersGuide_26_iterators.ipynb
index 7c3e7c148..32ad1c46f 100644
--- a/documentation/source/usersGuide/usersGuide_26_iterators.ipynb
+++ b/documentation/source/usersGuide/usersGuide_26_iterators.ipynb
@@ -617,6 +617,7 @@
     "* :attr:`~music21.stream.iterator.StreamIterator.notes` which filters out everything but `Note` and `Chord` objects\n",
     "* :attr:`~music21.stream.iterator.StreamIterator.notesAndRests` which filters out everything except `GeneralNote` objects\n",
     "* :attr:`~music21.stream.iterator.StreamIterator.parts` which returns all the `Part` objects\n",
+    "* :attr:`~music21.stream.iterator.StreamIterator.voices`\n",
     "* :attr:`~music21.stream.iterator.StreamIterator.voices` which returns all the `Voice` objects\n",
     "* :attr:`~music21.stream.iterator.StreamIterator.spanners` which returns all the `Spanner` objects"
    ]
diff --git a/music21/chord/tables.py b/music21/chord/tables.py
index 35af16626..ddc96df6d 100644
--- a/music21/chord/tables.py
+++ b/music21/chord/tables.py
@@ -1959,7 +1959,7 @@ class Test(unittest.TestCase):
 
 # ------------------------------------------------------------------------------
 # define presented order in documentation
-_DOC_ORDER = [addressToForteName, addressToPrimeForm, seekChordTablesAddress]
+_DOC_ORDER = [addressToForteName, addressToPrimeForm, addressToForteName, seekChordTablesAddress]
 
 
 if __name__ == '__main__':
diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py
index 362cc6d26..045159630 100644
--- a/music21/common/numberTools.py
+++ b/music21/common/numberTools.py
@@ -146,7 +146,7 @@ def numToIntOrFloat(value: t.Union[int, float]) -> t.Union[int, float]:
 
 DENOM_LIMIT = defaults.limitOffsetDenominator
 
-@lru_cache(None)
+@lru_cache(1024)
 def _preFracLimitDenominator(n: int, d: int) -> t.Tuple[int, int]:
     # noinspection PyShadowingNames
     '''
@@ -197,8 +197,6 @@ def _preFracLimitDenominator(n: int, d: int) -> t.Tuple[int, int]:
 
     (n.b. -- nothing printed)
     '''
-    # TODO: when Python 3.9 is the minimum version, replace lru_cache with simply cache,
-    #     which is the same speed as lru_cache(None) (it simply calls it)
     nOrg = n
     dOrg = d
     if d <= DENOM_LIMIT:  # faster than hard-coding 65535
@@ -218,27 +216,16 @@ def _preFracLimitDenominator(n: int, d: int) -> t.Tuple[int, int]:
     bound2n = p1
     bound2d = q1
     # s = (0.0 + n)/d
-    bound1minusS_n = abs((bound1n * dOrg) - (nOrg * bound1d))
-    bound1minusS_d = dOrg * bound1d
-    bound2minusS_n = abs((bound2n * dOrg) - (nOrg * bound2d))
-    bound2minusS_d = dOrg * bound2d
-    difference = (bound1minusS_n * bound2minusS_d) - (bound2minusS_n * bound1minusS_d)
-    if difference >= 0:
+    bound1minusS = (abs((bound1n * dOrg) - (nOrg * bound1d)), (dOrg * bound1d))
+    bound2minusS = (abs((bound2n * dOrg) - (nOrg * bound2d)), (dOrg * bound2d))
+    difference = (bound1minusS[0] * bound2minusS[1]) - (bound2minusS[0] * bound1minusS[1])
+    if difference > 0:
         # bound1 is farther from zero than bound2; return bound2
-        return (bound2n, bound2d)
+        return (p1, q1)
     else:
-        return (bound1n, bound1d)
+        return (p0 + k * p1, q0 + k * q1)
 
 
-# _KNOWN_PASSES is all values from whole to 64th notes with 0 or 1 dot
-# the length of this set does determine the time to search.  A set with all values from maxima to
-# 2048th notes + 1-2 dots was half the speed
-
-_KNOWN_PASSES = frozenset([
-    0.0625, 0.09375, 0.125, 0.1875,
-    0.25, 0.375, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0
-])
-
 # no type checking due to accessing protected attributes (for speed)
 @t.no_type_check
 def opFrac(num: t.Union[OffsetQLIn, None]) -> t.Union[OffsetQL, None]:
@@ -284,14 +271,6 @@ def opFrac(num: t.Union[OffsetQLIn, None]) -> t.Union[OffsetQL, None]:
     # This is a performance critical operation, tuned to go as fast as possible.
     # hence redundancy -- first we check for type (no inheritance) and then we
     # repeat exact same test with inheritance.
-    #
-    # Cannot use functools's Caching mechanisms on this because then it will
-    # return the same Fraction object for all calls, which is a problem in case
-    # anyone sets ._numeration or ._denominator directly on that object.
-    #
-    if num in _KNOWN_PASSES:
-        return num + 0.0   # need the add, because ints and Fractions satisfy the "in"
-
     # Note that the later examples are more verbose
     numType = type(num)
     if numType is float:
@@ -304,7 +283,6 @@ def opFrac(num: t.Union[OffsetQLIn, None]) -> t.Union[OffsetQL, None]:
         # unused_numerator, denominator = num.as_integer_ratio()  # too slow
         ir = num.as_integer_ratio()
         if ir[1] > DENOM_LIMIT:  # slightly faster[SIC!] than hard coding 65535!
-            # _preFracLimitDenominator uses a cache
             return Fraction(*_preFracLimitDenominator(*ir))  # way faster!
             # return Fraction(*ir).limit_denominator(DENOM_LIMIT) # *ir instead of float--can happen
             # internally in Fraction constructor, but is twice as fast...
@@ -321,7 +299,7 @@ def opFrac(num: t.Union[OffsetQLIn, None]) -> t.Union[OffsetQL, None]:
     elif num is None:
         return None
 
-    # class inheritance only check AFTER "type is" checks... this is redundant but highly optimized.
+    # class inheritance only check AFTER ifs... this is redundant but highly optimized.
     elif isinstance(num, int):
         return num + 0.0
     elif isinstance(num, float):
@@ -332,9 +310,9 @@ def opFrac(num: t.Union[OffsetQLIn, None]) -> t.Union[OffsetQL, None]:
             return num
 
     elif isinstance(num, Fraction):
-        d = num.denominator  # Use properties since it is a subclass
+        d = num._denominator  # private access instead of property: 6x faster; may break later...
         if (d & (d - 1)) == 0:  # power of two...
-            return num.numerator / (d + 0.0)  # 50% faster than float(num)
+            return num._numerator / (d + 0.0)  # 50% faster than float(num)
         else:
             return num  # leave fraction alone
     else:
diff --git a/music21/environment.py b/music21/environment.py
index ba0d617fd..b98afee7d 100644
--- a/music21/environment.py
+++ b/music21/environment.py
@@ -403,7 +403,7 @@ class _EnvironmentCore:
                  common.cleanpath(r'%PROGRAMFILES%\MuseScore 3\MuseScore.exe')
                  ),
             ]:
-                self[name] = value  # use for key checking
+                self.__setitem__(name, value)  # use for key checking
         elif platform == 'nix':
             for name, value in [
                 ('lilypondPath', '/usr/bin/lilypond'),
@@ -412,7 +412,7 @@ class _EnvironmentCore:
                 ('graphicsPath', '/usr/bin/xdg-open'),
                 ('pdfPath', '/usr/bin/xdg-open')
             ]:
-                self[name] = value  # use for key checking
+                self.__setitem__(name, value)  # use for key checking
         elif platform == 'darwin':
             versionTuple = common.macOSVersion()
             if versionTuple[0] >= 11 or (versionTuple[0] == 10 and versionTuple[1] >= 15):
@@ -432,7 +432,7 @@ class _EnvironmentCore:
                 ('musescoreDirectPNGPath',
                  '/Applications/MuseScore 3.app/Contents/MacOS/mscore'),
             ]:
-                self[name] = value  # use for key checking
+                self.__setitem__(name, value)  # use for key checking
 
     def _checkAccessibility(self, path: t.Optional[t.Union[str, pathlib.Path]]) -> bool:
         '''
@@ -1064,7 +1064,7 @@ class Environment:
         The first arg can be a list of strings or a string; lists are
         concatenated with common.formatStr().
         '''
-        if envSingleton()['debug'] >= statusLevel:
+        if envSingleton().__getitem__('debug') >= statusLevel:
             if isinstance(msg, str):
                 msg = [msg]  # make into a list
             if msg[0] != self.modNameParent and self.modNameParent is not None:
diff --git a/music21/features/jSymbolic.py b/music21/features/jSymbolic.py
index e9ef29954..956dc6dd2 100644
--- a/music21/features/jSymbolic.py
+++ b/music21/features/jSymbolic.py
@@ -618,7 +618,7 @@ class DurationOfMelodicArcsFeature(featuresModule.FeatureExtractor):
     (not counting unisons) divided by the number of times the melody
     changes direction.
 
-    Example: C D E D C D E C C
+    Example: C D E D C E D C C
     Intervals: [0] 2 2 -2 -2 2 2 -4 0
     Changes direction (equivalent to +/- sign) three times.
     There are seven non-unison (nonzero) intervals.
diff --git a/music21/key.py b/music21/key.py
index ffeb3ba96..a6d58f568 100644
--- a/music21/key.py
+++ b/music21/key.py
@@ -997,18 +997,15 @@ class Key(KeySignature, scale.DiatonicScale):
 
     def __eq__(self, other):
         '''
-        Two Keys are equal if their tonics are equal and their modes are equal.
-
-        Changed in v8: keys now compare equal regardless of the octave of their tonics:
-
-        >>> k = key.Key(pitch.Pitch('C4'))
-        >>> k2 = key.Key(pitch.Pitch('C5'))
-        >>> k == k2
-        True
+        two Keys are equal if their tonics are equal and their modes are equal
         '''
-        if not isinstance(other, Key):
-            return NotImplemented
-        return self.tonic.name == other.tonic.name and self.mode == other.mode
+        try:
+            if self.tonic == other.tonic and self.mode == other.mode:
+                return True
+            else:
+                return False
+        except AttributeError:
+            return False
 
     @property
     def relative(self) -> Key:
diff --git a/music21/lily/translate.py b/music21/lily/translate.py
index 4df9bb2f0..5aefbfaaf 100644
--- a/music21/lily/translate.py
+++ b/music21/lily/translate.py
@@ -2593,7 +2593,6 @@ class Test(unittest.TestCase):
         lpc.loadObjectFromScore(s)
 
     def testColors(self):
-        # pylint: disable=implicit-str-concat
         red_note = note.Note()
         red_note.style.color = '#FF0000'
         sm = LilypondConverter().lySimpleMusicFromNoteOrRest(red_note)
diff --git a/music21/midi/realtime.py b/music21/midi/realtime.py
index 27f2768f7..aa134ec2a 100644
--- a/music21/midi/realtime.py
+++ b/music21/midi/realtime.py
@@ -160,14 +160,14 @@ class StreamPlayer:  # pragma: no cover
         start_time = self.pygame.time.get_ticks()
         while self.pygame.mixer.music.get_busy():
             if busyFunction is not None:
-                busyFunction(busyArgs)
+                busyFunction.__call__(busyArgs)
             if self.pygame.time.get_ticks() - start_time > playForMilliseconds:
                 self.pygame.mixer.music.stop()
                 break
             pygameClock.tick(framerate)
 
         if endFunction is not None:
-            endFunction(endArgs)
+            endFunction.__call__(endArgs)
 
     def stop(self):
         self.pygame.mixer.music.stop()
diff --git a/music21/midi/translate.py b/music21/midi/translate.py
index 7970f4424..4a3b9b26d 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -35,7 +35,7 @@ from music21 import pitch
 from music21 import stream
 from music21 import tempo
 
-from music21.instrument import Conductor, UnpitchedPercussion, deduplicate
+from music21.instrument import Conductor, deduplicate
 from music21.midi.percussion import MIDIPercussionException, PercussionMapper
 
 environLocal = environment.Environment('midi.translate')
@@ -419,9 +419,7 @@ def midiEventsToNote(
     return t.cast(t.Union[note.Note, note.Unpitched], nr)
 
 
-def noteToMidiEvents(
-    inputM21: t.Union[note.Note, note.Unpitched], *, includeDeltaTime=True, channel=1
-) -> t.List[t.Union['music21.midi.DeltaTime', 'music21.midi.MidiEvent']]:
+def noteToMidiEvents(inputM21: note.Note, *, includeDeltaTime=True, channel=1):
     # noinspection PyShadowingNames
     '''
     Translate a music21 Note to a list of four MIDI events --
@@ -460,7 +458,6 @@ def noteToMidiEvents(
      <music21.midi.MidiEvent NOTE_OFF, track=None, channel=9, pitch=61, velocity=0>]
 
     Changed in v7 -- made keyword-only.
-    Changed in v8 -- added support for :class:`~music21.note.Unpitched`
     '''
     from music21 import midi as midiModule
 
@@ -477,13 +474,9 @@ def noteToMidiEvents(
     me1 = midiModule.MidiEvent(track=mt)
     me1.type = midiModule.ChannelVoiceMessages.NOTE_ON
     me1.channel = channel
-
-    if isinstance(n, note.Unpitched):
-        me1.pitch = _get_unpitched_pitch_value(n)
-    else:
-        me1.pitch = n.pitch.midi
-        if not n.pitch.isTwelveTone():
-            me1.centShift = n.pitch.getCentShiftFromMidi()
+    me1.pitch = n.pitch.midi
+    if not n.pitch.isTwelveTone():
+        me1.centShift = n.pitch.getCentShiftFromMidi()
 
     # TODO: not yet using dynamics or velocity
     # volScalar = n.volume.getRealized(useDynamicContext=False,
@@ -504,8 +497,9 @@ def noteToMidiEvents(
     me2 = midiModule.MidiEvent(track=mt)
     me2.type = midiModule.ChannelVoiceMessages.NOTE_OFF
     me2.channel = channel
-    me2.pitch = me1.pitch
-    me2.centShift = me1.centShift
+    me2.pitch = n.pitch.midi
+    if not n.pitch.isTwelveTone():
+        me2.centShift = n.pitch.getCentShiftFromMidi()
 
     me2.velocity = 0  # must be zero
     eventList.append(me2)
@@ -652,9 +646,7 @@ def midiEventsToChord(
     return c
 
 
-def chordToMidiEvents(
-    inputM21: chord.ChordBase, *, includeDeltaTime=True, channel=1
-) -> t.List[t.Union['music21.midi.DeltaTime', 'music21.midi.MidiEvent']]:
+def chordToMidiEvents(inputM21, *, includeDeltaTime=True, channel=1):
     # noinspection PyShadowingNames
     '''
     Translates a :class:`~music21.chord.Chord` object to a
@@ -683,21 +675,21 @@ def chordToMidiEvents(
      <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=83, velocity=0>]
 
     Changed in v7 -- made keyword-only.
-    Changed in v8 -- added support for :class:`~music21.percussion.PercussionChord`
     '''
     from music21 import midi as midiModule
     mt = None  # midi track
-    eventList: t.List[t.Union[midiModule.DeltaTime, midiModule.MidiEvent]] = []
+    eventList = []
     c = inputM21
 
     # temporary storage for setting correspondence
-    noteOn: t.List[midiModule.MidiEvent] = []
-    noteOff: t.List[midiModule.MidiEvent] = []
+    noteOn = []
+    noteOff = []
 
     chordVolume = c.volume  # use if component volume are not defined
     hasComponentVolumes = c.hasComponentVolumes()
 
-    for i, chordComponent in enumerate(c):
+    for i in range(len(c)):
+        chordComponent = c[i]
         # pitchObj = c.pitches[i]
         # noteObj = chordComponent
         if includeDeltaTime:
@@ -711,15 +703,9 @@ def chordToMidiEvents(
         me = midiModule.MidiEvent(track=mt)
         me.type = midiModule.ChannelVoiceMessages.NOTE_ON
         me.channel = 1
-        if isinstance(chordComponent, note.Note):
-            me.pitch = chordComponent.pitch.midi
-            if not chordComponent.pitch.isTwelveTone():
-                me.centShift = chordComponent.pitch.getCentShiftFromMidi()
-        elif isinstance(chordComponent, note.Unpitched):
-            me.pitch = _get_unpitched_pitch_value(chordComponent)
-        else:  # pragma: no cover
-            raise TypeError('ChordBase can only contain Note and Unpitched as members')
-
+        me.pitch = chordComponent.pitch.midi
+        if not chordComponent.pitch.isTwelveTone():
+            me.centShift = chordComponent.pitch.getCentShiftFromMidi()
         # if 'volume' in chordComponent:
 
         if hasComponentVolumes:
@@ -738,7 +724,9 @@ def chordToMidiEvents(
         noteOn.append(me)
 
     # must create each note on in chord before each note on
-    for i, noteOnEvent in enumerate(noteOn):
+    for i in range(len(c.pitches)):
+        pitchObj = c.pitches[i]
+
         if includeDeltaTime:
             # add note off / velocity zero message
             dt = midiModule.DeltaTime(track=mt)
@@ -750,8 +738,9 @@ def chordToMidiEvents(
         me = midiModule.MidiEvent(track=mt)
         me.type = midiModule.ChannelVoiceMessages.NOTE_OFF
         me.channel = channel
-        me.pitch = noteOnEvent.pitch
-        me.centShift = noteOnEvent.centShift
+        me.pitch = pitchObj.midi
+        if not pitchObj.isTwelveTone():
+            me.centShift = pitchObj.getCentShiftFromMidi()
         me.velocity = 0  # must be zero
         eventList.append(me)
         noteOff.append(me)
@@ -765,21 +754,6 @@ def chordToMidiEvents(
     return eventList
 
 
-def _get_unpitched_pitch_value(unp: note.Unpitched) -> int:
-    unpitched_instrument = None
-    if isinstance(unp.storedInstrument, instrument.UnpitchedPercussion):
-        unpitched_instrument = unp.storedInstrument
-    else:
-        if unp._chordAttached is not None:
-            unpitched_instrument = unp._chordAttached.getContextByClass(
-                instrument.UnpitchedPercussion)
-        else:
-            unpitched_instrument = unp.getContextByClass(instrument.UnpitchedPercussion)
-    if unpitched_instrument is not None and unpitched_instrument.percMapPitch is not None:
-        return unpitched_instrument.percMapPitch
-    return 60  # e.g. lossy instrument recognition from musicxml
-
-
 # ------------------------------------------------------------------------------
 def instrumentToMidiEvents(inputM21,
                            includeDeltaTime=True,
@@ -1270,7 +1244,6 @@ def elementToMidiEventList(
     '''
     # TODO: this is the best use of the switch statement when minimum Python
     #    version is 3.10
-    sub: t.Optional[t.List[t.Union['music21.midi.DeltaTime', 'music21.midi.MidiEvent']]]
     if isinstance(el, note.Rest):
         return None
     elif isinstance(el, note.Note):
@@ -1278,13 +1251,10 @@ def elementToMidiEventList(
         # using this property here is easier than using the above conversion
         # methods, as we do not need to know what the object is
         sub = noteToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, note.Unpitched):
-        sub = noteToMidiEvents(el, includeDeltaTime=False, channel=10)
+    # TODO: unpitched
     elif isinstance(el, chord.Chord):
         # TODO: skip Harmony unless showAsChord
         sub = chordToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, percussion.PercussionChord):
-        sub = chordToMidiEvents(el, includeDeltaTime=False, channel=10)
     elif isinstance(el, dynamics.Dynamic):
         return None  # dynamics have already been applied to notes
     elif isinstance(el, meter.TimeSignature):
@@ -2429,24 +2399,15 @@ def packetStorageFromSubstreamList(
         # get a first instrument; iterate over rest
         instrumentStream = subs.getElementsByClass(instrument.Instrument)
 
-        instObj = None
         # if there is an Instrument object at the start, make instObj that instrument
         # this may be a Conductor object if prepareStreamForMidi() was run
         if instrumentStream and subs.elementOffset(instrumentStream[0]) == 0:
             instObj = instrumentStream[0]
-        # If there is no such instrument, or a really lousy generic one without a midiProgram,
-        # check if this stream has any Unpitched objects. Make instObj an UnpitchedPercussion.
-        if (instrumentStream is None
-            # lousy instrument e.g. "music21.instrument.Instrument: ch10 jazzdrums1"
-            or (instObj is not None and instObj.midiProgram is None)
-            ) and subs.getElementsByClass(
-                (note.Unpitched, percussion.PercussionChord)):
-            # This dummy instance will be enough to get a channel 10 default in
-            # assignPacketsToChannels(). Later, the proper instrument in the stream will be read
-            instObj = UnpitchedPercussion()
         elif trackId == 0 and not subs.notesAndRests:
             # maybe prepareStreamForMidi() wasn't run; create Conductor instance
             instObj = Conductor()
+        else:
+            instObj = None
 
         trackPackets = streamToPackets(subs, trackId=trackId, addStartDelay=addStartDelay)
         # store packets in dictionary; keys are trackIds
@@ -2475,9 +2436,7 @@ def updatePacketStorageWithChannelInfo(
                 initCh = channelByInstrument[None]
             except KeyError:  # pragma: no cover
                 initCh = 1  # fallback, should not happen.
-        elif isinstance(instObj, instrument.UnpitchedPercussion):
-            initCh = 10
-        elif isinstance(instObj, instrument.Conductor):
+        elif 'Conductor' in instObj.classes:
             initCh = None
         else:  # keys are midi program
             initCh = channelByInstrument[instObj.midiProgram]
@@ -4057,48 +4016,6 @@ class Test(unittest.TestCase):
         meta_event_pairs = getMetaEvents([(DUMMY_DELTA_TIME, event2)])
         self.assertEqual(meta_event_pairs[0][1].midiProgram, 53)
 
-    def testExportUnpitched(self):
-        from music21 import midi as midiModule
-
-        m = stream.Measure(
-            [instrument.BassDrum(),
-            note.Unpitched(),
-            percussion.PercussionChord([note.Unpitched(), note.Unpitched()])]
-        )
-        trks = streamHierarchyToMidiTracks(m)
-        bd_trk = trks[1]
-
-        self.assertTrue({ev.channel for ev in bd_trk.events}, {10})
-        note_ons = [
-            ev for ev in bd_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
-        self.assertEqual(len(note_ons), 3)
-        self.assertEqual({ev.pitch for ev in note_ons}, {35})
-
-        # Replace the BassDrum with a vague instrument (lossy import)
-        m.pop(0)
-        m.insert(0, instrument.Instrument())
-
-        trks = streamHierarchyToMidiTracks(m)
-        drum_trk = trks[1]
-
-        self.assertTrue({ev.channel for ev in drum_trk.events}, {10})
-        note_ons = [
-            ev for ev in drum_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
-        self.assertEqual(len(note_ons), 3)
-        self.assertEqual({ev.pitch for ev in note_ons}, {60})  # fallback
-
-        # Change the stored instrument: affects that note only
-        m.notes.first().storedInstrument = instrument.Agogo()
-
-        trks = streamHierarchyToMidiTracks(m)
-        mixed_trk = trks[1]
-
-        self.assertTrue({ev.channel for ev in mixed_trk.events}, {10})
-        note_ons = [
-            ev for ev in mixed_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
-        self.assertEqual(len(note_ons), 3)
-        self.assertEqual([ev.pitch for ev in note_ons], [67, 60, 60])
-
 
 # ------------------------------------------------------------------------------
 _DOC_ORDER = [streamToMidiFile, midiFileToStream]
diff --git a/music21/musicxml/partStaffExporter.py b/music21/musicxml/partStaffExporter.py
index fc7bfefb9..ede1e1b4f 100644
--- a/music21/musicxml/partStaffExporter.py
+++ b/music21/musicxml/partStaffExporter.py
@@ -504,7 +504,7 @@ class PartStaffExporterMixin:
         Multiple meters (not very well supported by MusicXML readers):
 
         >>> from music21.musicxml import testPrimitive
-        >>> s = converter.parse(testPrimitive.pianoStaffPolymeter)
+        >>> s = converter.parse(testPrimitive.pianoStaffPolymeterWithClefOctaveChange)
         >>> SX = musicxml.m21ToXml.ScoreExporter(s)
         >>> root = SX.parse()
         >>> m1 = root.find('part/measure')
@@ -529,8 +529,9 @@ class PartStaffExporterMixin:
                 <line>2</line>
             </clef>
             <clef number="2">
-                <sign>F</sign>
-                <line>4</line>
+                <sign>G</sign>
+                <line>2</line>
+                <clef-octave-change>-1</clef-octave-change>
             </clef>
             </attributes>
         ...
@@ -615,6 +616,10 @@ class PartStaffExporterMixin:
                     newLine.text = (foundLine.text
                                     if (foundLine := oldClef.find('line')) is not None
                                     else '')
+                    foundOctave = oldClef.find('clef-octave-change')
+                    if foundOctave is not None:
+                        newOctave = SubElement(newClef, 'clef-octave-change')
+                        newOctave.text = foundOctave.text
                     helpers.insertBeforeElements(
                         mxAttributes,
                         newClef,
diff --git a/music21/note.py b/music21/note.py
index b7fe63057..c25300bf1 100644
--- a/music21/note.py
+++ b/music21/note.py
@@ -1776,7 +1776,6 @@ class Unpitched(NotRest):
 
     def __init__(self, displayName=None, **keywords):
         super().__init__(**keywords)
-        self._chordAttached: t.Optional['music21.percussion.PercussionChord'] = None
 
         self.displayStep: StepName = 'B'
         self.displayOctave: int = 4
diff --git a/music21/pitch.py b/music21/pitch.py
index fe724afc2..3f93fe6ef 100644
--- a/music21/pitch.py
+++ b/music21/pitch.py
@@ -4733,10 +4733,8 @@ class Pitch(prebase.ProtoM21Object):
 
     def updateAccidentalDisplay(
         self,
-        *,
         pitchPast: t.Optional[t.List['Pitch']] = None,
         pitchPastMeasure: t.Optional[t.List['Pitch']] = None,
-        otherSimultaneousPitches: t.Optional[t.List['Pitch']] = None,
         alteredPitches: t.Optional[t.List['Pitch']] = None,
         cautionaryPitchClass: bool = True,
         cautionaryAll: bool = False,
@@ -4757,9 +4755,6 @@ class Pitch(prebase.ProtoM21Object):
         `pitchPastMeasure` is a list of pitches preceding this pitch but in a
         previous measure. If None, a new list will be made.
 
-        `otherSimultaneousPitches` is a list of other pitches in this simultaneity, for use
-        when `cautionaryPitchClass` is True.
-
         The `alteredPitches` list supplies pitches from a :class:`~music21.key.KeySignature`
         object using the :attr:`~music21.key.KeySignature.alteredPitches` property.
         If None, a new list will be made.
@@ -4789,13 +4784,13 @@ class Pitch(prebase.ProtoM21Object):
 
         >>> a = pitch.Pitch('a')
         >>> past = [pitch.Pitch('a#'), pitch.Pitch('c#'), pitch.Pitch('c')]
-        >>> a.updateAccidentalDisplay(pitchPast=past, cautionaryAll=True)
+        >>> a.updateAccidentalDisplay(past, cautionaryAll=True)
         >>> a.accidental, a.accidental.displayStatus
         (<music21.pitch.Accidental natural>, True)
 
         >>> b = pitch.Pitch('a')
         >>> past = [pitch.Pitch('a#'), pitch.Pitch('c#'), pitch.Pitch('c')]
-        >>> b.updateAccidentalDisplay(pitchPast=past)  # should add a natural
+        >>> b.updateAccidentalDisplay(past)  # should add a natural
         >>> b.accidental, b.accidental.displayStatus
         (<music21.pitch.Accidental natural>, True)
 
@@ -4804,11 +4799,10 @@ class Pitch(prebase.ProtoM21Object):
 
         >>> a4 = pitch.Pitch('a4')
         >>> past = [pitch.Pitch('a#3'), pitch.Pitch('c#'), pitch.Pitch('c')]
-        >>> a4.updateAccidentalDisplay(pitchPast=past, cautionaryPitchClass=False)
+        >>> a4.updateAccidentalDisplay(past, cautionaryPitchClass=False)
         >>> a4.accidental is None
         True
 
-        v8 -- made keyword-only and added `otherSimultaneousPitches`.
         '''
         # N.B. -- this is a very complex method
         # do not alter it without significant testing.
@@ -4833,6 +4827,10 @@ class Pitch(prebase.ProtoM21Object):
         if alteredPitches is None:
             alteredPitches = []
 
+        # TODO: this presently deals with chords as simply a list
+        #    we might permit pitchPast to contain a list of pitches, to represent
+        #    a simultaneity?
+
         # should we display accidental if no previous accidentals have been displayed
         # i.e. if it's the first instance of an accidental after a tie
         displayAccidentalIfNoPreviousAccidentals = False
@@ -4863,15 +4861,6 @@ class Pitch(prebase.ProtoM21Object):
             else:
                 return  # exit: nothing more to do
 
-        if (
-            otherSimultaneousPitches
-            and cautionaryPitchClass
-            and any(pSimult.step == self.step and pSimult.pitchClass != self.pitchClass
-                for pSimult in otherSimultaneousPitches)
-        ):
-            set_displayStatus(True)
-            return
-
         # no pitches in past...
         if not pitchPastAll:
             # if we have no past, we show the accidental if this pitch name
@@ -5072,6 +5061,13 @@ class Pitch(prebase.ProtoM21Object):
                       and pPastInMeasure is False):
                     set_displayStatus(True)
 
+                # Avoid making early, incorrect assumptions if this pitch is part of a chord.
+                # pPast might include this note itself, in which case we should not say
+                # "natural already in past usage". (Filtering out the current note from pPast
+                # when calling this method is not sufficient, because there could be repetitions.)
+                elif self._client is not None and self._client._chordAttached is not None:
+                    continue
+
                 # other cases: already natural in past usage, do not need
                 # natural again (and not in key sig)
                 else:
diff --git a/music21/scale/intervalNetwork.py b/music21/scale/intervalNetwork.py
index b784facc4..5c1202c74 100644
--- a/music21/scale/intervalNetwork.py
+++ b/music21/scale/intervalNetwork.py
@@ -72,8 +72,7 @@ class Direction(enum.Enum):
         return 'Direction.' + self.name
 
 
-CacheKey = t.Tuple[
-    t.Union[int, Terminus], str, t.Union[str, None], t.Union[str, None], bool, t.Optional[bool]]
+CacheKey = t.Tuple[t.Union[int, Terminus], str, t.Union[str, None], t.Union[str, None], bool]
 
 
 def _gte(a, b):
@@ -1342,7 +1341,6 @@ class IntervalNetwork:
         minPitch: t.Optional[pitch.Pitch],
         maxPitch: t.Optional[pitch.Pitch],
         includeFirst: bool,
-        reverse: t.Optional[bool] = None,  # only meaningful for descending
     ) -> CacheKey:
         '''
         Return key for caching based on critical components.
@@ -1360,9 +1358,7 @@ class IntervalNetwork:
                 pitchReference.nameWithOctave,
                 minKey,
                 maxKey,
-                includeFirst,
-                reverse,
-                )
+                includeFirst)
 
     def realizeAscending(
         self,
@@ -1639,9 +1635,7 @@ class IntervalNetwork:
                                    pitchRef,
                                    minPitchObj,
                                    maxPitchObj,
-                                   includeFirst,
-                                   reverse,
-                                   )
+                                   includeFirst)
             if ck in self._descendingCache:
                 return self._descendingCache[ck]
 
@@ -1919,10 +1913,8 @@ class IntervalNetwork:
             mergedPitches, mergedNodes = pre + post, preNodeId + postNodeId
 
         if reverse:
-            # Make new objects, because this value might be cached in intervalNetwork's
-            # _descendingCache, and mutating it would be dangerous.
-            mergedPitches = list(reversed(mergedPitches))
-            mergedNodes = list(reversed(mergedNodes))
+            mergedPitches.reverse()
+            mergedNodes.reverse()
 
         return mergedPitches, mergedNodes
 
diff --git a/music21/search/lyrics.py b/music21/search/lyrics.py
index e127af47a..33f8e15d7 100644
--- a/music21/search/lyrics.py
+++ b/music21/search/lyrics.py
@@ -371,9 +371,7 @@ class LyricSearcher:
     #     return lineBreakStart
 
     def _reSearch(self, r: re.Pattern) -> t.List[SearchMatch]:
-        locations: t.List[SearchMatch] = []
-        if self._indexText is None:
-            return locations
+        locations = []
         for m in r.finditer(self._indexText):
             absoluteFoundPos, absoluteEndPos = m.span()
             matchText = m.group(0)
diff --git a/music21/search/segment.py b/music21/search/segment.py
index ce69a7ff0..9aa89191c 100644
--- a/music21/search/segment.py
+++ b/music21/search/segment.py
@@ -271,7 +271,7 @@ def loadScoreDict(filePath):
     if not isinstance(filePath, pathlib.Path):
         filePath = pathlib.Path(filePath)
 
-    with filePath.open('rb') as f:
+    with filePath.open('b') as f:
         scoreDict = json.load(f)
     return scoreDict
 
diff --git a/music21/stream/base.py b/music21/stream/base.py
index a7f8a090e..cf42ceee8 100644
--- a/music21/stream/base.py
+++ b/music21/stream/base.py
@@ -413,7 +413,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
         Generally you don't need this, just iterate over a stream, but it is necessary
         to add custom filters to an iterative search before iterating.
         '''
-        return iter(self)
+        return self.__iter__()
 
     @overload
     def __getitem__(self, k: str) -> iterator.RecursiveIterator[M21ObjType]:
@@ -6319,7 +6319,6 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
         *,
         pitchPast: t.Optional[t.List[pitch.Pitch]] = None,
         pitchPastMeasure: t.Optional[t.List[pitch.Pitch]] = None,
-        otherSimultaneousPitches: t.Optional[t.List[pitch.Pitch]] = None,
         useKeySignature: t.Union[bool, key.KeySignature] = True,
         alteredPitches: t.Optional[t.List[pitch.Pitch]] = None,
         searchKeySignatureByContext: bool = False,
@@ -6337,8 +6336,6 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
 
         `pitchPastMeasure` is a list of pitches preceding this pitch but in a previous measure.
 
-        `otherSimultaneousPitches` is a list of other pitches in this simultaneity, for use
-        when `cautionaryPitchClass` is True.
 
         If `useKeySignature` is True, a :class:`~music21.key.KeySignature` will be searched
         for in this Stream or this Stream's defined contexts. An alternative KeySignature
@@ -6380,7 +6377,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
 
         Changed in v.6: does not return anything if inPlace is True.
         Changed in v.7: default inPlace is False
-        Changed in v.8: altered unisons/octaves in Chords now supply clarifying naturals.
+        Changed in v.8: altered unisons in Chords now supply clarifying naturals.
 
         All arguments are keyword only.
         '''
@@ -6462,9 +6459,11 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
                     tiePitchSet.add(e.pitch.nameWithOctave)
 
             elif isinstance(e, chord.Chord):
+                # add all chord elements to past first
                 # when reading a chord, this will apply an accidental
                 # if pitches in the chord suggest an accidental
                 seenPitchNames = set()
+                pitchPast += e.pitches
 
                 for n in list(e):
                     p = n.pitch
@@ -6473,12 +6472,9 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
                     else:
                         lastNoteWasTied = False
 
-                    otherSimultaneousPitches = [other for other in e.pitches if other is not p]
-
                     p.updateAccidentalDisplay(
                         pitchPast=pitchPast,
                         pitchPastMeasure=pitchPastMeasure,
-                        otherSimultaneousPitches=otherSimultaneousPitches,
                         alteredPitches=alteredPitches,
                         cautionaryPitchClass=cautionaryPitchClass,
                         cautionaryAll=cautionaryAll,
@@ -6493,7 +6489,6 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
                 for pName in seenPitchNames:
                     tiePitchSet.add(pName)
 
-                pitchPast += e.pitches
             else:
                 tiePitchSet.clear()
 
@@ -8650,7 +8645,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
         if recurse is True:
             sIterator = post.recurse()
         else:
-            sIterator = iter(post)
+            sIterator = post.__iter__()
 
         if classFilterList:
             sIterator = sIterator.addFilter(filters.ClassFilter(classFilterList))
diff --git a/music21/tinyNotation.py b/music21/tinyNotation.py
index 954643032..49fedc124 100644
--- a/music21/tinyNotation.py
+++ b/music21/tinyNotation.py
@@ -229,6 +229,7 @@ for details on how to do that.
 import collections
 import copy
 import re
+import sre_parse
 import typing
 import unittest
 
@@ -1123,7 +1124,7 @@ class Converter:
         for rePre, classCall in self.tokenMap:
             try:
                 self._tokenMapRe.append((re.compile(rePre), classCall))
-            except re.error as e:
+            except sre_parse.error as e:
                 raise TinyNotationException(
                     f'Error in compiling token, {rePre}: {e}'
                 ) from e


Test patch:
diff --git a/documentation/source/developerReference/testing.ipynb b/documentation/source/developerReference/testing.ipynb
index 9a17d5083..e95d5f713 100644
--- a/documentation/source/developerReference/testing.ipynb
+++ b/documentation/source/developerReference/testing.ipynb
@@ -210,7 +210,7 @@
     "\n",
     "    https://coveralls.io/github/cuthbertLab/music21\n",
     "\n",
-    "While 100% code coverage is impossible, increasing the coverage percentage with each new contribution is essential.  We're at 93% code coverage and get the green badge of pride on our README.md file, and we like that!\n",
+    "While 100% code coverage is impossible, increasing the coverage percentage with each new contribution is essential.  We're at 92% code coverage and get the green badge of pride on our README.md file, and we like that!\n",
     "\n",
     "If something should be impossible to trigger, but the code is being maintained for some reason, you can add \"`# pragma: no cover`\" to the line or to the fork, but this is generally a no-no."
    ]
diff --git a/music21/mei/test_base.py b/music21/mei/test_base.py
index c63bc0c0f..3319cdbc3 100644
--- a/music21/mei/test_base.py
+++ b/music21/mei/test_base.py
@@ -17,6 +17,9 @@ Tests for :mod:`music21.mei.base`.
 # this often happens on TestCase subclasses
 # pylint: disable=too-many-public-methods
 
+# a test that uses only assertions on the Mocks will have no-self-use
+# pylint: disable=no-self-use
+
 # if we mock many things, this may be triggered
 # pylint: disable=too-many-arguments
 
diff --git a/music21/musicxml/testPrimitive.py b/music21/musicxml/testPrimitive.py
index 6f7c6eac0..4c7021ed7 100644
--- a/music21/musicxml/testPrimitive.py
+++ b/music21/musicxml/testPrimitive.py
@@ -18115,6 +18115,59 @@ pianoStaffPolymeter = '''<?xml version="1.0" encoding="ISO-8859-1" standalone="n
 </score-partwise>
 '''
 
+# pianoStaffPolymeterWithClefOctaveChange is a copy of pianoStaffPolymeter (above)
+#   with clef #2 changed from BassClef to Treble8vbClef.
+
+pianoStaffPolymeterWithClefOctaveChange = (
+    '''<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 0.6b Partwise//EN"
+ "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise>
+    <identification>
+        <miscellaneous>
+            <miscellaneous-field name="description">Polymeter on a grand staff</miscellaneous-field>
+        </miscellaneous>
+    </identification>
+    <part-list>
+        <score-part id="P1">
+            <part-name>MusicXML Part</part-name>
+        </score-part>
+    </part-list>
+    <part id="P1">
+        <measure number="1">
+            <attributes>
+                <divisions>96</divisions>
+                <key><fifths>0</fifths></key>
+                <time number="1"><beats>4</beats><beat-type>4</beat-type></time>
+                <time number="2"><beats>2</beats><beat-type>2</beat-type></time>
+                <staves>2</staves>
+                <clef number="1"><sign>G</sign><line>2</line></clef>
+                <clef number="2">
+                    <sign>G</sign>
+                    <line>2</line>
+                    <clef-octave-change>-1</clef-octave-change>
+                </clef>
+            </attributes>
+            <note>
+                <pitch><step>F</step><octave>4</octave></pitch>
+                <duration>384</duration>
+                <voice>1</voice>
+                <type>whole</type>
+                <staff>1</staff>
+            </note>
+            <backup><duration>384</duration></backup>
+            <note>
+                <pitch><step>B</step><octave>2</octave></pitch>
+                <duration>384</duration>
+                <voice>2</voice>
+                <type>whole</type>
+                <staff>2</staff>
+            </note>
+        </measure>
+    </part>
+</score-partwise>
+''')
+
 
 ALL = [
     articulations01, pitches01a, directions31a, lyricsMelisma61d, notations32a,  # 0
diff --git a/music21/scale/test_intervalNetwork.py b/music21/scale/test_intervalNetwork.py
index ec509fdbd..f4c7e6208 100644
--- a/music21/scale/test_intervalNetwork.py
+++ b/music21/scale/test_intervalNetwork.py
@@ -594,20 +594,6 @@ class Test(unittest.TestCase):
                 getNeighbor=Direction.DESCENDING)),
             'A-4')
 
-    def test_realize_descending_reversed_cached(self):
-        net = IntervalNetwork()
-        net.fillMelodicMinor()
-
-        descending_melodic_minor, _ = net.realizeDescending(
-            'C4', minPitch='C4', maxPitch='C5', reverse=False)
-        self.assertEqual(descending_melodic_minor[0].nameWithOctave, 'B-4')
-        self.assertEqual(descending_melodic_minor[-1].nameWithOctave, 'C4')
-
-        descending_melodic_minor_reversed, _ = net.realizeDescending(
-            'C4', minPitch='C4', maxPitch='C5', reverse=True)
-        self.assertEqual(descending_melodic_minor_reversed[0].nameWithOctave, 'C4')  # was B-4
-        self.assertEqual(descending_melodic_minor_reversed[-1].nameWithOctave, 'B-4')  # was C4
-
 
 # ------------------------------------------------------------------------------
 if __name__ == '__main__':
diff --git a/music21/scale/test_scale_main.py b/music21/scale/test_scale_main.py
index 7c346e5c5..0e98f543b 100644
--- a/music21/scale/test_scale_main.py
+++ b/music21/scale/test_scale_main.py
@@ -302,13 +302,16 @@ class Test(unittest.TestCase):
         self.assertEqual(self.pitchOut(mm.getPitches('c1', 'c3', direction=Direction.DESCENDING)),
                          '[C3, B2, A2, G2, F2, E2, D2, C2, B1, A1, G1, F1, E1, D1, C1]')
 
+        # TODO: this shows a problem with a bidirectional scale: we are
+        # always starting at the tonic and moving up or down; so this is still
+        # giving a descended portion, even though an ascending portion was requested
         # noinspection PyArgumentList
         self.assertEqual(self.pitchOut(mm.getPitches('c1', 'c3', direction=Direction.ASCENDING)),
                          '[C1, D1, E1, F#1, G#1, A1, B1, C2, D2, E2, F#2, G#2, A2, B2, C3]')
 
         # noinspection PyArgumentList
         self.assertEqual(self.pitchOut(mm.getPitches('c1', 'c3', direction=Direction.DESCENDING)),
-                         '[C3, B2, A2, G2, F2, E2, D2, C2, B1, A1, G1, F1, E1, D1, C1]')
+                         '[C1, D1, E1, F1, G1, A1, B1, C2, D2, E2, F2, G2, A2, B2, C3]')
 
         # noinspection PyArgumentList
         self.assertEqual(self.pitchOut(mm.getPitches('a5', 'a6', direction=Direction.ASCENDING)),
@@ -900,19 +903,6 @@ Franck Jedrzejewski continued fractions approx. of 12-tet
         with self.assertRaises(intervalNetwork.IntervalNetworkException):
             e.deriveRanked(['C4', 'E4', 'G4'], comparisonAttribute='name')
 
-    def test_getPitches_multiple_times(self):
-        """Worth testing because we found cached lists being mutated."""
-        c_maj = scale.MajorScale('C')
-
-        for i in range(3):  # catch both even and odd states
-            with self.subTest(iterations=i):
-                # due to a tight coupling of direction and reverse, this sets reverse=True
-                # when calling getRealization()
-                post = c_maj.getPitches(direction=Direction.DESCENDING)
-
-                self.assertEqual(post[0].nameWithOctave, 'C5')
-                self.assertEqual(post[-1].nameWithOctave, 'C4')
-
 
 # ------------------------------------------------------------------------------
 if __name__ == '__main__':
diff --git a/music21/stream/tests.py b/music21/stream/tests.py
index 35f8b7bc0..40d95fbcb 100644
--- a/music21/stream/tests.py
+++ b/music21/stream/tests.py
@@ -2613,8 +2613,10 @@ class Test(unittest.TestCase):
         self.assertTrue(c1.pitches[1].accidental.displayStatus)
         self.assertTrue(c1.pitches[2].accidental.displayStatus)
 
-        # not necessary to repeat the natural afterward
-        self.assertIsNone(n3.pitch.accidental, None)
+        # not getting a natural here because of chord tones
+        # self.assertTrue(n3.pitch.accidental.displayStatus)
+        # self.assertEqual(n3.pitch.accidental, None)
+        # s.show()
 
         s = Stream()
         n1 = note.Note('a#')
@@ -2792,35 +2794,6 @@ class Test(unittest.TestCase):
 
         # TODO: other types
 
-    def testMakeAccidentalsOnChord(self):
-        c = chord.Chord('F# A# C#')
-        s = Stream(c)
-        self.assertFalse(any(n.pitch.accidental.displayStatus for n in c))
-        s.makeAccidentals(inPlace=True)
-        self.assertTrue(all(n.pitch.accidental.displayStatus for n in c))
-
-        augmented_octave = chord.Chord('F4 F#5')
-        s2 = Stream(augmented_octave)
-        self.assertIsNone(augmented_octave[0].pitch.accidental)
-        s2.makeAccidentals(inPlace=True)
-        self.assertTrue(augmented_octave[0].pitch.accidental.displayStatus)
-
-        # Repeat the test without octaves and reset state
-        low, high = augmented_octave.pitches
-        low.octave = None
-        low.accidental = None
-        high.octave = None
-        high.accidental.displayStatus = None
-
-        s2.makeAccidentals(inPlace=True)
-        self.assertTrue(all(n.pitch.accidental.displayStatus for n in augmented_octave))
-
-        # Repeat the test with two F#'s in key of 1 sharp
-        two_f_sharps = chord.Chord('F#4 F#5')
-        s3 = Stream([key.KeySignature(1), two_f_sharps])
-        s3.makeAccidentals(inPlace=True)
-        self.assertTrue(all(n.pitch.accidental.displayStatus is False for n in two_f_sharps))
-
     def testMakeNotationTiesKeyless(self):
         p = converter.parse('tinynotation: 4/4 f#1~ f#1')
         # Key of no sharps/flats
diff --git a/music21/test/test_pitch.py b/music21/test/test_pitch.py
index 888d34b0b..7247ab797 100644
--- a/music21/test/test_pitch.py
+++ b/music21/test/test_pitch.py
@@ -89,7 +89,7 @@ class Test(unittest.TestCase):
         self.assertEqual(a.name, 'C')
         self.assertTrue(a.accidental.displayStatus)
 
-        a.updateAccidentalDisplay(pitchPast=past, overrideStatus=True)
+        a.updateAccidentalDisplay(past, overrideStatus=True)
         self.assertFalse(a.accidental.displayStatus)
 
         b = copy.deepcopy(a)
@@ -101,7 +101,7 @@ class Test(unittest.TestCase):
         '''
         def proc(_pList, past):
             for p in _pList:
-                p.updateAccidentalDisplay(pitchPast=past)
+                p.updateAccidentalDisplay(past)
                 past.append(p)
 
         def compare(past, _result):
@@ -206,7 +206,7 @@ class Test(unittest.TestCase):
         '''
         def proc(_pList, past, alteredPitches):
             for p in _pList:
-                p.updateAccidentalDisplay(pitchPast=past, alteredPitches=alteredPitches)
+                p.updateAccidentalDisplay(past, alteredPitches=alteredPitches)
                 past.append(p)
 
         def compare(past, _result):
@@ -321,13 +321,13 @@ class Test(unittest.TestCase):
         '''
         def proc1(_pList, _past):
             for p in _pList:
-                p.updateAccidentalDisplay(pitchPast=_past, cautionaryPitchClass=True,
+                p.updateAccidentalDisplay(_past, cautionaryPitchClass=True,
                                           cautionaryNotImmediateRepeat=False)
                 _past.append(p)
 
         def proc2(_pList, _past):
             for p in _pList:
-                p.updateAccidentalDisplay(pitchPast=_past, cautionaryPitchClass=False,
+                p.updateAccidentalDisplay(_past, cautionaryPitchClass=False,
                                           cautionaryNotImmediateRepeat=False)
                 _past.append(p)
 
@@ -366,7 +366,7 @@ class Test(unittest.TestCase):
         a4 = Pitch('a4')
         past = [Pitch('a#3'), Pitch('c#'), Pitch('c')]
         # will not add a natural because match is pitchSpace
-        a4.updateAccidentalDisplay(pitchPast=past, cautionaryPitchClass=False)
+        a4.updateAccidentalDisplay(past, cautionaryPitchClass=False)
         self.assertEqual(a4.accidental, None)
 
     def testAccidentalsCautionary(self):