Repository: cuthbertLab/music21
Pull request #922 (2021-03-18 15:43:43)

Add classFilterList to makeTies and allow makeRests to put rests in Measures
Noticed that we can solve 2 TODOs at once: 

- `makeRests()` makes rests inside Measures now
- in order to do that, we need to split rests at measure boundaries, which is what `makeTies()` does, but we don't want to touch the notes, therefore it was convenient to just handle the TODO declared there to create a class filter list.
- Update: now this also fixes some issues with MIDI notation making:
-- measures are now repositioned in the stream if they were enlarged due to gap-filling
-- ties are now made on notes and chords across barlines (vs. just rests, by virtue of this PR's new feature in makeRests())

jacobtylerwalls: This exercise illuminated for me why I had to call `makeRests()` per measure when making the MIDI improvements recently:

https://github.com/cuthbertLab/music21/blob/f2c3dc16b7946309b33d72e2e2ddcecb7f73b332/music21/midi/translate.py#L1894-L1899

I think I can leave this as is (no harm in calling per-measure).
coveralls: 
[![Coverage Status](https://coveralls.io/builds/38055474/badge)](https://coveralls.io/builds/38055474)

Coverage increased (+0.009%) to 92.133% when pulling **c4edd23869673d6de5b9492e39e4b594497f4200 on jacobtylerwalls:makeTies-classList** into **f2c3dc16b7946309b33d72e2e2ddcecb7f73b332 on cuthbertLab:master**.

mscuthbert: This looks spectacular to me!  But do we have a test on what happens if we want to do something like:

```
part.insert(0, 4/4)
part.insert(9, Note())

part.makeNotation()
```

or 

```
part.makeMeasures()
part.makeRests()
```

or how to make sure that we end up with:

```
0-4 Measure w/ whole rest
4-8 Measure w/ whole rest
8-12 Measure with Qtr Rest, Qtr Note,Half rest
   or 8-10 Measure with Qtr Rest, Qtr Note
```

Let me know if this is too abbreviated to follow.
jacobtylerwalls: Great test; this was sketchy before, looks like rests need to be made a second time, but should work once the extra call is made

PR
```
>>> p = stream.Part()
>>> p.insert(0, meter.TimeSignature('4/4')))
>>> p.insert(9, note.Note())
>>> made = p.makeNotation()
>>> made.show('t')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.meter.TimeSignature 4/4>
{4.0} <music21.stream.Measure 2 offset=4.0>

{8.0} <music21.stream.Measure 3 offset=8.0>
    {1.0} <music21.note.Note C>
    {2.0} <music21.bar.Barline type=final>
>>> makeRestsAgain = made.makeRests()
>>> makeRestsAgain.show('t')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.meter.TimeSignature 4/4>
    {0.0} <music21.note.Rest rest>
{4.0} <music21.stream.Measure 2 offset=4.0>
    {0.0} <music21.note.Rest rest>
{8.0} <music21.stream.Measure 3 offset=8.0>
    {0.0} <music21.note.Rest rest>
    {1.0} <music21.note.Note C>
    {2.0} <music21.note.Rest rest>
    {4.0} <music21.bar.Barline type=final>
```

versus current development branch

```
>>> p = stream.Part()
>>> p.insert(0, meter.TimeSignature('4/4'))
>>> p.insert(9, note.Note())
>>> made = p.makeNotation()
>>> made.show('t')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.meter.TimeSignature 4/4>
{4.0} <music21.stream.Measure 2 offset=4.0>

{8.0} <music21.stream.Measure 3 offset=8.0>
    {1.0} <music21.note.Note C>
    {2.0} <music21.bar.Barline type=final>
>>> makeRestsAgain = made.makeRests()
>>> makeRestsAgain.show('t')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.clef.TrebleClef>
    {0.0} <music21.meter.TimeSignature 4/4>
{4.0} <music21.stream.Measure 2 offset=4.0>    <----------- no change

{8.0} <music21.stream.Measure 3 offset=8.0>
    {1.0} <music21.note.Note C>
    {2.0} <music21.bar.Barline type=final>
```
jacobtylerwalls: Got it, `makeNotation()` doesn't call `makeRests()`, you have to ask for them. But once rests are made in your example, on this PR they look good; I'll add a test.

```
>>> p = stream.Part()
>>> p.insert(0, meter.TimeSignature())
>>> p.insert(9, note.Note())
>>> p.makeNotation(inPlace=True)
<music21.stream.Part 0x10bd70b80>
>>> p.makeRests(fillGaps=True, inPlace=True, timeRangeFromBarDuration=True)
>>> p.show('t', addEndTimes=True)
{0.0 - 4.0} <music21.stream.Measure 1 offset=0.0>
    {0.0 - 0.0} <music21.clef.TrebleClef>
    {0.0 - 0.0} <music21.meter.TimeSignature 4/4>
    {0.0 - 4.0} <music21.note.Rest rest>
{4.0 - 8.0} <music21.stream.Measure 2 offset=4.0>
    {0.0 - 4.0} <music21.note.Rest rest>
{8.0 - 12.0} <music21.stream.Measure 3 offset=8.0>
    {0.0 - 1.0} <music21.note.Rest rest>
    {1.0 - 2.0} <music21.note.Note C>
    {2.0 - 4.0} <music21.note.Rest rest>
    {4.0 - 4.0} <music21.bar.Barline type=final>
```
jacobtylerwalls: If I take this existing test and just run it on the top-level stream, it's the case you and I were discussing.

Here is the failure if I run this adjusted test against main, showing no rests in m. 2:

```
======================================================================
FAIL: testMakeRestsB (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jwalls/music21/music21/stream/tests.py", line 1915, in testMakeRestsB
    self.assertEqual(match,
AssertionError: '[(1.0, <music21.note.Note C>, <music21.dura[16 chars]0>)]' != '[(0.0, <music21.note.Rest rest>, <music21.d[148 chars]0>)]'
- [(1.0, <music21.note.Note C>, <music21.duration.Duration 1.0>)]
+ [(0.0, <music21.note.Rest rest>, <music21.duration.Duration 1.0>), (1.0, <music21.note.Note C>, <music21.duration.Duration 1.0>), (2.0, <music21.note.Rest rest>, <music21.duration.Duration 2.0>)]
```

LMK if you'd prefer I write a new test. I think it's good for tests to model the expected flow -- to just call this on the top level like the other notation routines.

Patch:
diff --git a/documentation/docbuild/writers.py b/documentation/docbuild/writers.py
index 9642380e0..091675965 100644
--- a/documentation/docbuild/writers.py
+++ b/documentation/docbuild/writers.py
@@ -229,7 +229,7 @@ class IPythonNotebookReSTWriter(ReSTWriter):
     This class wraps the 3rd-party ``nbconvert`` Python script.
     '''
     def __init__(self):
-        from .iterators import IPythonNotebookIterator  # @UnresolvedImport
+        from .iterators import IPythonNotebookIterator
         super(IPythonNotebookReSTWriter, self).__init__()
         self.ipythonNotebookFilePaths = list(IPythonNotebookIterator())
         # Do not run self.setupOutputDirectory()
@@ -450,7 +450,7 @@ class IPythonNotebookReSTWriter(ReSTWriter):
             str(self.sourceToAutogenerated(ipythonNotebookFilePath))
         )[0]
 
-        app = nb.NbConvertApp.instance()  # @UndefinedVariable
+        app = nb.NbConvertApp.instance()
         app.initialize(argv=['--to', 'rst', '--output', outputPath,
                              str(ipythonNotebookFilePath)])
         app.writer.build_directory = str(ipythonNotebookFilePath.parent)
diff --git a/documentation/source/usersGuide/usersGuide_12_music21object.ipynb b/documentation/source/usersGuide/usersGuide_12_music21object.ipynb
index 3ef4fea1c..33e2fd442 100644
--- a/documentation/source/usersGuide/usersGuide_12_music21object.ipynb
+++ b/documentation/source/usersGuide/usersGuide_12_music21object.ipynb
@@ -850,7 +850,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A `Music21Object` that is inside one or more Streams should be able to get its most recently stream via its `.activeSite` attribute.  We've put `n` in `s`, which is called (now incorrectly) `'empty stream'`, so n's `.activeSite` should be `s`."
+    "A `Music21Object` that is inside one or more Streams should be able to get its most recently referenced stream via its `.activeSite` attribute.  We've put `n` in `s`, which is called (now incorrectly) `'empty stream'`, so n's `.activeSite` should be `s`."
    ]
   },
   {
diff --git a/music21/_version.py b/music21/_version.py
index 7af8793f9..420e32879 100644
--- a/music21/_version.py
+++ b/music21/_version.py
@@ -42,7 +42,7 @@ When changing, update the single test case in base.py.
 Changing this number invalidates old pickles -- do it if the old pickles create a problem.
 '''
 
-__version_info__ = (7, 0, 3)  # can be 4-tuple: (7, 0, 5, 'a2')
+__version_info__ = (7, 0, 4)  # can be 4-tuple: (7, 0, 5, 'a2')
 
 v = '.'.join(str(x) for x in __version_info__[0:3])
 if len(__version_info__) > 3 and __version_info__[3]:
diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py
index fe16ed5e1..630ef6709 100644
--- a/music21/analysis/discrete.py
+++ b/music21/analysis/discrete.py
@@ -1537,7 +1537,7 @@ class Test(unittest.TestCase):
         from music21 import converter
         from music21.musicxml import testFiles
         # use a musicxml test file with independently confirmed results
-        s = converter.parse(testFiles.edgefield82b)  # @UndefinedVariable
+        s = converter.parse(testFiles.edgefield82b)
 
         p = KrumhanslSchmuckler()
         k = p.getSolution(s)
diff --git a/music21/analysis/floatingKey.py b/music21/analysis/floatingKey.py
index 143e8904b..b8970fffa 100644
--- a/music21/analysis/floatingKey.py
+++ b/music21/analysis/floatingKey.py
@@ -59,6 +59,22 @@ class KeyAnalyzer:
      <music21.key.Key of f# minor>, <music21.key.Key of f# minor>, <music21.key.Key of f# minor>,
      <music21.key.Key of f# minor>, <music21.key.Key of f# minor>,
      <music21.key.Key of f# minor>, <music21.key.Key of f# minor>]
+
+    Fixed in v.7 -- analysis now incorporates final measures in pieces without pickup measures:
+
+    >>> tiny = converter.parse('tinyNotation: c1 e1 g1 c1 d-4 d-4 d-4 d-4')
+    >>> ka = analysis.floatingKey.KeyAnalyzer(tiny)
+    >>> ka.windowSize = 1
+    >>> ka.run()  # This previously only gave four elements: am, CM, CM, CM
+    [<music21.key.Key of a minor>, <music21.key.Key of C major>, <music21.key.Key of C major>,
+     <music21.key.Key of C major>, <music21.key.Key of b- minor>]
+
+    No measures will fail.
+
+    >>> s = stream.Part([note.Note()])
+    >>> ka = analysis.floatingKey.KeyAnalyzer(s)
+    Traceback (most recent call last):
+    music21.analysis.floatingKey.FloatingKeyException: Stream must have Measures inside it
     '''
     def __init__(self, s=None):
         if s is None:
@@ -70,7 +86,7 @@ class KeyAnalyzer:
 
         self.weightAlgorithm = divide
         if s.hasPartLikeStreams():
-            p = s.parts.first()
+            p = s.iter.parts.first()
         else:
             p = s
         self.numMeasures = len(p.getElementsByClass('Measure'))  # could be wrong for endings, etc.
@@ -84,7 +100,8 @@ class KeyAnalyzer:
     def getRawKeyByMeasure(self):
         keyByMeasure = []
         for i in range(self.numMeasures):
-            m = self.stream.measure(i)
+            # now `m` is a measure-slice of the entire stream
+            m = self.stream.measure(i, indicesNotNumbers=True)
             if m is None or not m.recurse().notes:
                 k = None
             else:
@@ -93,15 +110,16 @@ class KeyAnalyzer:
         self.rawKeyByMeasure = keyByMeasure
         return keyByMeasure
 
-    def getInterpretationByMeasure(self, mNumber):
+    def getInterpretationByMeasure(self, mIndex):
         '''
         Returns a dictionary of interpretations for the measure.
+        `mIndex` is 0-indexed.
         '''
-        if mNumber in self._interpretationMeasureDict:
-            return self._interpretationMeasureDict[mNumber]  # CACHE
+        if mIndex in self._interpretationMeasureDict:
+            return self._interpretationMeasureDict[mIndex]  # CACHE
         if not self.rawKeyByMeasure:
             self.getRawKeyByMeasure()
-        mk = self.rawKeyByMeasure[mNumber]
+        mk = self.rawKeyByMeasure[mIndex]
         if mk is None:
             return None
         # noinspection PyDictCreation
@@ -109,7 +127,7 @@ class KeyAnalyzer:
         interpretations[mk.tonicPitchNameWithCase] = mk.correlationCoefficient
         for otherKey in mk.alternateInterpretations:
             interpretations[otherKey.tonicPitchNameWithCase] = otherKey.correlationCoefficient
-        self._interpretationMeasureDict[mNumber] = interpretations
+        self._interpretationMeasureDict[mIndex] = interpretations
         return copy.copy(interpretations)  # for manipulating
 
     def smoothInterpretationByMeasure(self):
diff --git a/music21/articulations.py b/music21/articulations.py
index fa46714b4..e47f5990e 100644
--- a/music21/articulations.py
+++ b/music21/articulations.py
@@ -83,6 +83,7 @@ import unittest
 
 from music21 import base
 from music21 import common
+from music21.common.classTools import tempAttribute
 from music21 import exceptions21
 from music21 import environment
 from music21 import style
@@ -295,9 +296,8 @@ class Spiccato(Staccato, Accent):
     '''
     def __init__(self):
         Staccato.__init__(self)
-        storedLengthShift = self.lengthShift
-        Accent.__init__(self)  # order matters...
-        self.lengthShift = storedLengthShift
+        with tempAttribute(self, 'lengthShift'):
+            Accent.__init__(self)  # order matters...
 
 
 class Tenuto(LengthArticulation):
diff --git a/music21/base.py b/music21/base.py
index 3cb5a73ea..7e59f8f62 100644
--- a/music21/base.py
+++ b/music21/base.py
@@ -28,7 +28,7 @@ available after importing `music21`.
 <class 'music21.base.Music21Object'>
 
 >>> music21.VERSION_STR
-'7.0.3'
+'7.0.4'
 
 Alternatively, after doing a complete import, these classes are available
 under the module "base":
diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py
index 7edbd2159..e137b4f2f 100644
--- a/music21/chord/__init__.py
+++ b/music21/chord/__init__.py
@@ -1681,24 +1681,45 @@ class Chord(note.NotRest):
         # c2.getZRelation()  # returns a list in non-ET12 space...
         # <music21.chord.ForteSet at 0x234892>
 
-    def hasAnyRepeatedDiatonicNote(
-        self,
-        *,
-        testRoot=None
-    ) -> bool:
+    def hasAnyEnharmonicSpelledPitches(self) -> bool:
+        '''
+        Returns True if for any given pitchClass there is at most one spelling of the note
+        (in any octave).
+
+        >>> cChord = chord.Chord('C4 E4 G4 C5')
+        >>> cChord.hasAnyEnharmonicSpelledPitches()
+        False
+
+        Notice that having a C in two different octaves is no problem.  However, this is False:
+
+        >>> cChord = chord.Chord('C4 E4 G4 B#4')
+        >>> cChord.hasAnyEnharmonicSpelledPitches()
+        True
+        '''
+        if len(self._unorderedPitchClasses()) != len(set(p.name for p in self.pitches)):
+            return True
+        else:
+            return False
+
+    def hasAnyRepeatedDiatonicNote(self) -> bool:
         '''
         Returns True if for any diatonic note (e.g., C or C# = C) there are two or more
         different notes (such as E and E-) in the chord. If there are no repeated
-        scale degrees, return false.
+        scale degrees, return False.
 
-        >>> cChord = chord.Chord (['C', 'E', 'E-', 'G'])
+        >>> cChord = chord.Chord(['C', 'E', 'E-', 'G'])
         >>> cChord.hasAnyRepeatedDiatonicNote()
         True
 
-        This returns False because chromatically identical notes of
-        different scale degrees do not count.
+        This routine is helpful for anything that works with Generic intervals and chord
+        steps such as `.third` which makes sure that checking for root, second, third,
+        ..., seventh will actually find all the different notes.
+
+        This following example returns False because chromatically identical notes of
+        different scale degrees do not count as a repeated diatonic note.
+        (See :meth:`~music21.chord.Chord.hasAnyEnharmonicSpelledPitches` for that method)
 
-        >>> other = chord.Chord (['C', 'E', 'F-', 'G'])
+        >>> other = chord.Chord(['C', 'E', 'F-', 'G'])
         >>> other.hasAnyRepeatedDiatonicNote()
         False
         '''
@@ -1757,7 +1778,7 @@ class Chord(note.NotRest):
         or more different notes (such as E and E-) in it.  Otherwise
         returns False.
 
-        >>> cChord = chord.Chord (['G2', 'E4', 'E-5', 'C6'])
+        >>> cChord = chord.Chord(['G2', 'E4', 'E-5', 'C6'])
         >>> cChord.hasRepeatedChordStep(3)
         True
 
@@ -2011,11 +2032,11 @@ class Chord(note.NotRest):
         else:
             raise ChordException('Not a triad or Seventh, cannot determine inversion.')
 
-    @cacheMethod
-    def isAugmentedSixth(self):
+
+    def isAugmentedSixth(self, *, permitAnyInversion=False):
         '''
-        returns True if the chord is an Augmented 6th chord in first inversion.
-        (N.B. a French/Swiss sixth technically needs to be in second inversion)
+        returns True if the chord is an Augmented 6th chord in normal inversion.
+        (that is, in first inversion for Italian and German and second for French and Swiss)
 
         >>> c = chord.Chord(['A-3', 'C4', 'E-4', 'F#4'])
         >>> c.isAugmentedSixth()
@@ -2029,22 +2050,28 @@ class Chord(note.NotRest):
         >>> c.isAugmentedSixth()
         False
 
-
         Italian...
 
         >>> c = chord.Chord(['A-3', 'C4', 'F#4'])
         >>> c.isAugmentedSixth()
         True
-        '''
 
-        if self.isItalianAugmentedSixth():
-            return True
-        elif self.isFrenchAugmentedSixth():
-            return True
-        elif self.isGermanAugmentedSixth():
-            return True
-        elif self.isSwissAugmentedSixth():
+        If `permitAnyInversion` is True then any inversion is allowed.
+
+        '''
+        # cardinality is just used to speed up the call to avoid checking multiple augmented
+        # 6ths on a triad, etc.  The fact that Ab C F# Gb will have cardinality of 3
+        # but fail isItalianAugmentedSixth is not a problem.
+        cardinality = self.pitchClassCardinality
+        if cardinality == 3 and self.isItalianAugmentedSixth(permitAnyInversion=permitAnyInversion):
             return True
+        if cardinality == 4:
+            if self.isFrenchAugmentedSixth(permitAnyInversion=permitAnyInversion):
+                return True
+            elif self.isGermanAugmentedSixth(permitAnyInversion=permitAnyInversion):
+                return True
+            elif self.isSwissAugmentedSixth(permitAnyInversion=permitAnyInversion):
+                return True
 
         return False
 
@@ -2263,6 +2290,10 @@ class Chord(note.NotRest):
 
         >>> chord.Chord().isDiminishedTriad()
         False
+        >>> other = chord.Chord(['C', 'E-', 'F#', 'G-'])
+        >>> other.isDiminishedTriad()
+        False
+
         '''
         return self._checkTriadType((3, 10, 0), 3, 6)
 
@@ -2319,8 +2350,7 @@ class Chord(note.NotRest):
         '''
         return self.chordTablesAddress[:3] == (4, 28, 0)
 
-    @cacheMethod
-    def isFrenchAugmentedSixth(self) -> bool:
+    def isFrenchAugmentedSixth(self, *, permitAnyInversion=False) -> bool:
         '''
         Returns True if the chord is a French augmented sixth chord
         (flat 6th scale degree in bass, tonic, second scale degree, and raised 4th).
@@ -2356,6 +2386,15 @@ class Chord(note.NotRest):
         >>> fr6c.isFrenchAugmentedSixth()
         False
 
+        Unless `permitAnyInversion` is True
+
+        >>> fr6c.isFrenchAugmentedSixth(permitAnyInversion=True)
+        True
+
+
+        Changed in v7: `permitAnyInversion` added
+
+
         OMIT_FROM_DOCS
 
         >>> chord.Chord().isFrenchAugmentedSixth()
@@ -2365,61 +2404,14 @@ class Chord(note.NotRest):
         >>> fr6d.isFrenchAugmentedSixth()
         False
         '''
-        if self.chordTablesAddress[:3] != (4, 25, 0):
-            return False
-
-        augSixthChord = self.removeRedundantPitchNames(inPlace=False)
-        # Fr+6 => Minor sixth scale step in bass, tonic, raised 4th + second scale degree.
-        try:
-            if not augSixthChord.inversion() == 2:
-                return False
-        except ChordException:
-            return False
-        augSixthChord.root(augSixthChord.getChordStep(3))
-        # Chord must be in first inversion.
-        if not augSixthChord.inversion() == 1:
-            return False
-        # Augmented sixth interval (simple or compound) must be present
-        # between bass and raised 4th (root of chord)
-        bass = augSixthChord.bass()  # might be caught by the try: except: above
-        root = augSixthChord.root()
-        if bass is None or root is None:
-            return False
-        augSixthInterval = interval.Interval(bass, root)
-        if not (augSixthInterval.diatonic.specificName == 'Augmented'
-                and augSixthInterval.generic.simpleDirected == 6):
-            return False
-        # The fifth of the chord must be the tonic.
-        # The fifth of the chord is the tonic if and only if
-        # there is a M3 (simple or compound) between the bass (m6 scale step)
-        # and the fifth of the chord.
-        tonic = augSixthChord.getChordStep(5)
-        if tonic is None:  # might not be possible now. # pragma: no cover
-            return False
-        majThirdInterval = interval.Interval(bass, tonic)
-        if not (majThirdInterval.diatonic.specificName == 'Major'
-                and majThirdInterval.generic.simpleDirected == 3):
-            return False
-        # The sixth of the chord must be the supertonic.
-        # The sixth of the chord is the supertonic if and only if
-        # there is a A4 (simple or compound) between the bass (m6 scale step) and
-        # the sixth of the chord.
-        supertonic = augSixthChord.getChordStep(6)
-        augFourthInterval = interval.Interval(bass, supertonic)
-        if supertonic is None:
-            return False
-        if (not (augFourthInterval.diatonic.specificName == 'Augmented'
-                 and augFourthInterval.generic.simpleDirected == 4)):
-            return False
-        # No other pitches may be present that aren't the m6 scale step,
-        # raised 4th, tonic, or supertonic.
-        for samplePitch in augSixthChord.pitches:
-            if samplePitch not in (bass, root, tonic, supertonic):
-                return False
-        return True
+        return self._isAugmentedSixthHelper(
+            (4, 25, 0),
+            2,
+            permitAnyInversion,
+            [('M3', 'm-6'), ('d5', 'A-4'), ('m7', 'M-2')]
+        )
 
-    @cacheMethod
-    def isGermanAugmentedSixth(self) -> bool:
+    def isGermanAugmentedSixth(self, *, permitAnyInversion=False) -> bool:
         '''
         Returns True if the chord is a German augmented sixth chord
         (flat 6th scale degree in bass, tonic, flat third scale degree, and raised 4th).
@@ -2429,7 +2421,7 @@ class Chord(note.NotRest):
         >>> gr6a.isGermanAugmentedSixth()
         True
 
-        Spelling matters (see isSwissAugmentedSixth
+        Spelling matters (see isSwissAugmentedSixth)
 
         >>> gr6b = chord.Chord(['A-3', 'C4', 'D#4', 'F#4'])
         >>> gr6b.isGermanAugmentedSixth()
@@ -2441,6 +2433,14 @@ class Chord(note.NotRest):
         >>> gr6c.isGermanAugmentedSixth()
         False
 
+        unless `permitAnyInversion` is True
+
+        >>> gr6c.isGermanAugmentedSixth(permitAnyInversion=True)
+        True
+
+        Changed in v7: `permitAnyInversion` added
+
+
         OMIT_FROM_DOCS
 
         >>> chord.Chord().isGermanAugmentedSixth()
@@ -2449,49 +2449,14 @@ class Chord(note.NotRest):
         >>> gr6d = chord.Chord(['A-3', 'C-4', 'E-4', 'F#4'])
         >>> gr6d.isGermanAugmentedSixth()
         False
-        '''
-        if self.chordTablesAddress[:3] != (4, 27, -1):
-            return False
-
-        augSixthChord = self.removeRedundantPitchNames(inPlace=False)
-        # Chord must be in first inversion.
-        if not augSixthChord.inversion() == 1:
-            return False
-
-        # Augmented sixth interval (simple or compound) must be present
-        # between bass and raised 4th (root of chord)
-        bass = augSixthChord.bass()
-        root = augSixthChord.root()
-        augSixthInterval = interval.Interval(bass, root)
-        if (not (augSixthInterval.diatonic.specificName == 'Augmented'
-                 and augSixthInterval.generic.simpleDirected == 6)):
-            return False
 
-        # The fifth of the chord must be the tonic.
-        # The fifth of the chord is the tonic if and only if
-        # there is a M3 (simple or compound) between the bass (m6 scale step)
-        # and the fifth of the chord.
-        tonic = augSixthChord.fifth
-        if tonic is None:
-            return False
-        majThirdInterval = interval.Interval(bass, tonic)
-        if (not (majThirdInterval.diatonic.specificName == 'Major'
-                 and majThirdInterval.generic.simpleDirected == 3)):
-            return False
-
-        # The seventh of the chord must be the mediant.
-        # The seventh of the chord is the mediant if and only if
-        # there is a P5 (simple or compound) between the bass
-        # (m6 scale step) and the fifth of the chord.
-        mediant = augSixthChord.seventh
-        if mediant is None:
-            return False
-        perfectFifthInterval = interval.Interval(bass, mediant)
-        if (not (perfectFifthInterval.diatonic.specificName == 'Perfect'
-                 and perfectFifthInterval.generic.simpleDirected == 5)):
-            return False
-
-        return True
+        '''
+        return self._isAugmentedSixthHelper(
+            (4, 27, -1),
+            1,
+            permitAnyInversion,
+            [('d3', 'A-6'), ('d5', 'A-4'), ('d7', 'A-2')]
+        )
 
     @cacheMethod
     def isHalfDiminishedSeventh(self) -> bool:
@@ -2628,7 +2593,7 @@ class Chord(note.NotRest):
 
         return True
 
-    def isItalianAugmentedSixth(self, restrictDoublings=False) -> bool:
+    def isItalianAugmentedSixth(self, *, restrictDoublings=False, permitAnyInversion=False) -> bool:
         '''
         Returns True if the chord is a properly spelled Italian augmented sixth chord in
         first inversion.  Otherwise returns False.
@@ -2654,12 +2619,11 @@ class Chord(note.NotRest):
         >>> c4.isItalianAugmentedSixth()
         False
 
-        If inversions don't matter to you, put the chord in another inversion:
+        If inversions don't matter to you, add `permitAnyInversion=True`:
 
-        >>> import copy
-        >>> c5 = copy.deepcopy(c4)
-        >>> c5.inversion(1)
-        >>> c5.isItalianAugmentedSixth()
+        >>> c3.isItalianAugmentedSixth(permitAnyInversion=True)
+        True
+        >>> c4.isItalianAugmentedSixth(permitAnyInversion=True)
         True
 
         If doubling rules are turned on then only the tonic can be doubled:
@@ -2672,53 +2636,79 @@ class Chord(note.NotRest):
         False
         >>> c5.isItalianAugmentedSixth(restrictDoublings=False)
         True
+
+        Changed in v7.  `restrictDoublings` is keyword only.  Added `permitAnyInversion`.
         '''
-        if self.chordTablesAddress[:3] != (3, 8, 1):
+        aug6check = self._isAugmentedSixthHelper(
+            (3, 8, 1),
+            1,
+            permitAnyInversion,
+            [('d3', 'A-6'), ('d5', 'A-4')]
+        )
+        if not aug6check:
             return False
 
-        # It+6 => Minor sixth scale step in bass, tonic, raised 4th + doubling of tonic note.
-        augSixthChord = self.removeRedundantPitchNames(inPlace=False)
+        if restrictDoublings:
+            root = self.root()
+            third = self.third
+            fifth = self.fifth
+
+            # only the tonic (that is, fifth) can be doubled...
+            for p in self.pitches:
+                if p.name == fifth.name:
+                    continue
+                if p is not third and p is not root:
+                    return False
+        return True
+
+    def _isAugmentedSixthHelper(
+        self,
+        chordTableAddress: Tuple[int, int, int],
+        requiredInversion: int,
+        permitAnyInversion: bool,
+        intervalsCheck: List[Tuple[str, str]],
+    ) -> bool:
+        '''
+        Helper method for simplifying checking Italian, German, etc. Augmented
+        Sixth chords
+        '''
+        if self.chordTablesAddress[:3] != chordTableAddress:
+            return False
+
+        if self.hasAnyEnharmonicSpelledPitches():
+            return False
 
         # Chord must be in first inversion.
-        if not augSixthChord.inversion() == 1:
+        try:
+            if not permitAnyInversion and self.inversion() != requiredInversion:
+                return False
+        except ChordException:
             return False
 
-        # Augmented sixth interval (simple or compound) must
-        # be present between bass and raised 4th (root of chord)
-        bass = augSixthChord.bass()
-        root = augSixthChord.root()
-        if bass is None or root is None:
+        root = self.root()
+        third = self.third
+        if third is None:
             return False
-        augSixthInterval = interval.Interval(bass, root)
-        if (not (augSixthInterval.diatonic.specificName == 'Augmented'
-                 and augSixthInterval.generic.simpleDirected == 6)):
+        thirdInterval = interval.Interval(root, third)
+        if thirdInterval.directedSimpleName not in intervalsCheck[0]:
             return False
 
-        # The fifth of the chord must be the tonic.
-        # The fifth of the chord is the tonic if and only if
-        # there is a M3 (simple or compound) between the bass (m6 scale step)
-        # and the fifth of the chord.
-        tonic = augSixthChord.getChordStep(5)
-        if tonic is None:
+        fifth = self.fifth
+        if fifth is None:
             return False
-        majThirdInterval = interval.Interval(bass, tonic)
-        if (not (majThirdInterval.diatonic.specificName == 'Major'
-                 and majThirdInterval.generic.simpleDirected == 3)):
+        fifthInterval = interval.Interval(root, fifth)
+        if fifthInterval.directedSimpleName not in intervalsCheck[1]:
             return False
 
-        # No other pitches may be present that aren't the m6 scale step, raised 4th, or tonic.
-        for samplePitch in augSixthChord.pitches:
-            if samplePitch not in (bass, root, tonic):
-                return False
+        if len(intervalsCheck) < 3:
+            return True
 
-        if restrictDoublings:
-            # only the tonic can be doubled...
-            for samplePitch in self.pitches:
-                if not (samplePitch.nameWithOctave == bass.nameWithOctave
-                        or samplePitch.nameWithOctave == root.nameWithOctave
-                        or samplePitch.nameWithOctave == tonic.nameWithOctave):
-                    if samplePitch.name != tonic.name:
-                        return False
+        seventh = self.seventh
+        if seventh is None:
+            return False
+        seventhInterval = interval.Interval(root, seventh)
+        if seventhInterval.directedSimpleName not in intervalsCheck[2]:
+            return False
 
         return True
 
@@ -2737,21 +2727,21 @@ class Chord(note.NotRest):
         if not self.isTriad():
             return False
 
+        if self.hasAnyEnharmonicSpelledPitches():
+            return False
+
         # these are cached, and guaranteed to be non-None by isTriad()
         third = self.third
         fifth = self.fifth
 
         root = self.root()
         rootPitchClass = root.pitchClass
-        names = (root.name, third.name, fifth.name)
-        for thisPitch in self.pitches:
-            thisInterval = (thisPitch.pitchClass - rootPitchClass) % 12
-            if (thisPitch is third) and thisInterval != thirdSemitones:
-                return False
-            if (thisPitch is fifth) and thisInterval != fifthSemitones:
-                return False
-            if thisPitch.name not in names:
-                return False
+        thirdInterval = (third.pitchClass - rootPitchClass) % 12
+        if thirdInterval != thirdSemitones:
+            return False
+        fifthInterval = (fifth.pitchClass - rootPitchClass) % 12
+        if fifthInterval != fifthSemitones:
+            return False
 
         return True
 
@@ -2979,8 +2969,7 @@ class Chord(note.NotRest):
 
         return True
 
-    @cacheMethod
-    def isSwissAugmentedSixth(self):
+    def isSwissAugmentedSixth(self, *, permitAnyInversion=False):
         '''
         Returns true is it is a respelled German augmented 6th chord with
         sharp 2 instead of flat 3.  This chord has many names,
@@ -2996,73 +2985,25 @@ class Chord(note.NotRest):
         >>> chord.Chord('A-4 C5 E-5 F#6').isSwissAugmentedSixth()
         False
 
-        Inversions DO matter:
+        Inversions matter:
 
-        >>> chord.Chord('F#4 D#5 C6 A-6').isSwissAugmentedSixth()
+        >>> ch3 = chord.Chord('F#4 D#5 C6 A-6')
+        >>> ch3.isSwissAugmentedSixth()
         False
-        '''
-        if self.chordTablesAddress[:3] != (4, 27, -1):
-            return False
-
-        # Sw+6 => Minor sixth scale step in bass, tonic, raised 4th + raised 2nd scale degree.
-        augSixthChord = self.removeRedundantPitchNames(inPlace=False)
 
-        # The root() method of music21.chord Chord determines the root based on the note with
-        # the most thirds above it. However, under this definition, a Swiss augmented sixth chord
-        # resembles a second inversion chord, not the first inversion subdominant chord it is based
-        # upon. We fix this by adjusting the root. First, however, we check to see if the chord is
-        # in second inversion to begin with, otherwise its not a Sw+6 chord. This is to avoid
-        # ChordException errors.
-        if not augSixthChord.inversion() == 2:
-            return False
-        augSixthChord.root(augSixthChord.getChordStep(3))
+        unless `permitAnyInversion` is given:
 
-        # Chord must be in first inversion.
-        if not augSixthChord.inversion() == 1:
-            return False
-
-        # Augmented sixth interval (simple or compound)
-        # must be present between bass and raised 4th (root of chord)
-        bass = augSixthChord.bass()
-        root = augSixthChord.root()
-        if bass is None or root is None:
-            return False
-        augSixthInterval = interval.Interval(bass, root)
-        if (not (augSixthInterval.diatonic.specificName == 'Augmented'
-                 and augSixthInterval.generic.simpleDirected == 6)):
-            return False
-
-        # The fifth of the chord must be the tonic.
-        # The fifth of the chord is the tonic if and only if
-        # there is a M3 (simple or compound) between the bass
-        # (m6 scale step) and the fifth of the chord.
-        tonic = augSixthChord.fifth
-        if tonic is None:
-            return False
-        majThirdInterval = interval.Interval(bass, tonic)
-        if (not (majThirdInterval.diatonic.specificName == 'Major'
-                 and majThirdInterval.generic.simpleDirected == 3)):
-            return False
-
-        # The sixth of the chord must be the supertonic. The sixth of the chord
-        # is the supertonic if and only if
-        # there is a A4 (simple or compound) between the bass (m6 scale step)
-        # and the sixth of the chord.
-        supertonic = augSixthChord.getChordStep(6)
-        augFourthInterval = interval.Interval(bass, supertonic)
-        if supertonic is None:
-            return False
-        if (not (augFourthInterval.diatonic.specificName == 'Doubly-Augmented'
-                 and augFourthInterval.generic.simpleDirected == 4)):
-            return False
-
-        # No other pitches may be present that aren't the m6 scale step, raised 4th,
-        # tonic, or supertonic.
-        for samplePitch in augSixthChord.pitches:
-            if samplePitch not in (bass, root, tonic, supertonic):
-                return False
+        >>> ch3.isSwissAugmentedSixth(permitAnyInversion=True)
+        True
 
-        return True
+        Changed in v7: `permitAnyInversion` added
+        '''
+        return self._isAugmentedSixthHelper(
+            (4, 27, -1),
+            2,
+            permitAnyInversion,
+            [('m3', 'M-6'), ('dd5', 'AA-4'), ('d7', 'A-2')]
+        )
 
 
     @cacheMethod
diff --git a/music21/common/classTools.py b/music21/common/classTools.py
index fe99e5c69..7ef739618 100644
--- a/music21/common/classTools.py
+++ b/music21/common/classTools.py
@@ -9,10 +9,14 @@
 # Copyright:    Copyright © 2009-2015 Michael Scott Cuthbert and the music21 Project
 # License:      BSD, see license.txt
 # ------------------------------------------------------------------------------
-from typing import Any, Type
+import contextlib
+from typing import Any, Type, Dict
 
 # from music21 import exceptions21
-__all__ = ['isNum', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet']
+__all__ = [
+    'isNum', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet',
+    'tempAttribute', 'saveAttributes',
+]
 
 
 def isNum(usrData: Any) -> bool:
@@ -166,6 +170,67 @@ def getClassSet(instance, classNameTuple=None):
     return classSet
 
 
+TEMP_ATTRIBUTE_SENTINEL = object()
+
+
+@contextlib.contextmanager
+def tempAttribute(obj, attribute: str, new_val=TEMP_ATTRIBUTE_SENTINEL):
+    '''
+    Temporarily set an attribute in an object to another value
+    and then restore it afterwards.
+
+    >>> p = pitch.Pitch('C4')
+    >>> p.midi
+    60
+    >>> with common.classTools.tempAttribute(p, 'nameWithOctave', 'D#5'):
+    ...     p.midi
+    75
+    >>> p.nameWithOctave
+    'C4'
+
+    Setting to a new value is optional.
+
+    For working with multiple attributes see :func:`~music21.classTools.saveAttributes`.
+
+    New in v7.
+    '''
+    tempStorage = getattr(obj, attribute)
+    if new_val is not TEMP_ATTRIBUTE_SENTINEL:
+        setattr(obj, attribute, new_val)
+    try:
+        yield
+    finally:
+        setattr(obj, attribute, tempStorage)
+
+@contextlib.contextmanager
+def saveAttributes(obj, *attributeList):
+    '''
+    Save a number of attributes in an object and then restore them afterwards.
+
+    >>> p = pitch.Pitch('C#2')
+    >>> with common.classTools.saveAttributes(p, 'name', 'accidental'):
+    ...     p.step = 'E'
+    ...     p.accidental = pitch.Accidental('flat')
+    ...     p.nameWithOctave
+    'E-2'
+    >>> p.nameWithOctave
+    'C#2'
+
+    For storing and setting a value on a single attribute see
+    :func:`~music21.classTools.tempAttribute`.
+
+    New in v7.
+    '''
+    tempStorage: Dict[str, Any] = {}
+    for attribute in attributeList:
+        tempStorage[attribute] = getattr(obj, attribute)
+    try:
+        yield
+    finally:
+        for k, v in tempStorage.items():  # dicts are ordered in 3.7
+            setattr(obj, k, v)
+
+
 # ------------------------------------------------------------------------------
 # define presented order in documentation
 # _DOC_ORDER = [fromRoman, toRoman]
diff --git a/music21/common/decorators.py b/music21/common/decorators.py
index 55c19ecfa..a9aafece5 100644
--- a/music21/common/decorators.py
+++ b/music21/common/decorators.py
@@ -175,5 +175,5 @@ def cacheMethod(method):
 
 
 if __name__ == '__main__':
-    import music21  # @Reimport
+    import music21
     music21.mainTest()
diff --git a/music21/common/enums.py b/music21/common/enums.py
index a1fb9ce00..84f51afa4 100644
--- a/music21/common/enums.py
+++ b/music21/common/enums.py
@@ -132,6 +132,8 @@ class OffsetSpecial(StrEnum):
     False
     >>> 'lowestOffset' in OffsetSpecial
     True
+    >>> str(OffsetSpecial.AT_END)
+    'highestTime'
 
     New in v7.
     '''
diff --git a/music21/common/fileTools.py b/music21/common/fileTools.py
index 6beb56d52..4c5e2cba9 100644
--- a/music21/common/fileTools.py
+++ b/music21/common/fileTools.py
@@ -43,10 +43,7 @@ def cd(targetDir):
 
     will switch temporarily, and then switch back when leaving.
     '''
-    try:
-        cwd = os.getcwdu()  # unicode # @UndefinedVariable
-    except AttributeError:
-        cwd = os.getcwd()  # non unicode
+    cwd = os.getcwd()
 
     try:
         os.chdir(targetDir)
diff --git a/music21/common/parallel.py b/music21/common/parallel.py
index 1579f1186..69a01847b 100644
--- a/music21/common/parallel.py
+++ b/music21/common/parallel.py
@@ -106,7 +106,7 @@ def runParallel(iterable, parallelFunction, *,
     # pylint: disable=not-callable
     numCpus = cpus()
 
-    if numCpus == 1 or multiprocessing.current_process().daemon:  # @UndefinedVariable
+    if numCpus == 1 or multiprocessing.current_process().daemon:
         return runNonParallel(iterable, parallelFunction,
                               updateFunction=updateFunction,
                               updateMultiply=updateMultiply,
@@ -214,7 +214,7 @@ def cpus():
     '''
     Returns the number of CPUs or if >= 3, one less (to leave something out for multiprocessing)
     '''
-    cpuCount = multiprocessing.cpu_count()  # @UndefinedVariable
+    cpuCount = multiprocessing.cpu_count()
     if cpuCount >= 3:
         return cpuCount - 1
     else:
@@ -250,7 +250,7 @@ class Test(unittest.TestCase):
     def x_figure_out_segfault_testMultiprocess(self):
         files = ['bach/bwv66.6', 'schoenberg/opus19', 'AcaciaReel']
         # for importing into testSingleCoreAll we need the full path to the modules
-        from music21.common.parallel import _countN, _countUnpacked  # @UnresolvedImport
+        from music21.common.parallel import _countN, _countUnpacked
         output = runParallel(files, _countN)
         self.assertEqual(output, [165, 50, 131])
         runParallel(files, _countN,
diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py
index 0b64e91c4..c44e5e13c 100644
--- a/music21/common/stringTools.py
+++ b/music21/common/stringTools.py
@@ -352,5 +352,5 @@ def removePunctuation(s: str) -> str:
 # -----------------------------------------------------------------------------
 
 if __name__ == '__main__':
-    import music21  # @Reimport
+    import music21
     music21.mainTest()
diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py
index 1df7dab69..c5da5e381 100644
--- a/music21/converter/__init__.py
+++ b/music21/converter/__init__.py
@@ -631,7 +631,8 @@ class Converter:
         self.subConverter.keywords = keywords
         self.subConverter.parseData(dataStr, number=number)
 
-    def parseURL(self, url, format=None, number=None, **keywords):  # @ReservedAssignment
+    def parseURL(self, url, *, format=None, number=None,
+                 forceSource=False, **keywords):  # @ReservedAssignment
         '''Given a url, download and parse the file
         into a music21 Stream stored in the `stream`
         property of the converter object.
@@ -639,11 +640,15 @@ class Converter:
         Note that this checks the user Environment
         `autoDownload` setting before downloading.
 
+        Use `forceSource=True` to download every time rather than read from a cached file.
+
         >>> jeanieLightBrownURL = ('https://github.com/cuthbertLab/music21/raw/master' +
         ...        '/music21/corpus/leadSheet/fosterBrownHair.mxl')
         >>> c = converter.Converter()
         >>> #_DOCS_SHOW c.parseURL(jeanieLightBrownURL)
         >>> #_DOCS_SHOW jeanieStream = c.stream
+
+        Changed in v.7 -- made keyword-only and added `forceSource` option.
         '''
         autoDownload = environLocal['autoDownload']
         if autoDownload in ('deny', 'ask'):
@@ -670,7 +675,7 @@ class Converter:
         dst = self._getDownloadFp(directory, ext, url)  # returns pathlib.Path
         urlretrieve = urllib.request.urlretrieve
 
-        if not dst.exists():
+        if forceSource is True or not dst.exists():
             try:
                 environLocal.printDebug(['downloading to:', str(dst)])
                 fp, unused_headers = urlretrieve(url, filename=str(dst))
@@ -1041,14 +1046,17 @@ def parseData(dataStr, number=None, format=None, **keywords):  # @ReservedAssign
 
 # pylint: disable=redefined-builtin
 # noinspection PyShadowingBuiltins
-def parseURL(url, number=None, format=None, forceSource=False, **keywords):  # @ReservedAssignment
+def parseURL(url, *, format=None, number=None,
+             forceSource=False, **keywords):  # @ReservedAssignment
     '''
     Given a URL, attempt to download and parse the file into a Stream. Note:
     URL downloading will not happen automatically unless the user has set their
     Environment "autoDownload" preference to "allow".
+
+    Changed in v.7 -- made keyword-only.
     '''
     v = Converter()
-    v.parseURL(url, format=format, **keywords)
+    v.parseURL(url, format=format, forceSource=forceSource, **keywords)
     return v.stream
 
 
@@ -1301,14 +1309,14 @@ class TestExternal(unittest.TestCase):  # pragma: no cover
 
     def testMusicXMLConversion(self):
         from music21.musicxml import testFiles
-        for mxString in testFiles.ALL:  # @UndefinedVariable
+        for mxString in testFiles.ALL:
             a = subConverters.ConverterMusicXML()
             a.parseData(mxString)
 
     def testMusicXMLTabConversion(self):
         from music21.musicxml import testFiles
 
-        mxString = testFiles.ALL[5]  # @UndefinedVariable
+        mxString = testFiles.ALL[5]
         a = subConverters.ConverterMusicXML()
         a.parseData(mxString)
 
@@ -1569,12 +1577,12 @@ class Test(unittest.TestCase):
     def testConversionMXMetadata(self):
         from music21.musicxml import testFiles
 
-        a = parse(testFiles.mozartTrioK581Excerpt)  # @UndefinedVariable
+        a = parse(testFiles.mozartTrioK581Excerpt)
         self.assertEqual(a.metadata.composer, 'Wolfgang Amadeus Mozart')
         self.assertEqual(a.metadata.title, 'Quintet for Clarinet and Strings')
         self.assertEqual(a.metadata.movementName, 'Menuetto (Excerpt from Second Trio)')
 
-        a = parse(testFiles.binchoisMagnificat)  # @UndefinedVariable
+        a = parse(testFiles.binchoisMagnificat)
         self.assertEqual(a.metadata.composer, 'Gilles Binchois')
         # this gets the best title available, even though this is movement title
         self.assertEqual(a.metadata.title, 'Excerpt from Magnificat secundi toni')
@@ -1678,7 +1686,7 @@ class Test(unittest.TestCase):
         # environLocal.printDebug(['\n' + 'opening fp', fp])
 
         self.assertEqual(len(s.flat.getElementsByClass(note.Note)), 2)
-        self.assertEqual(len(s.flat.getElementsByClass(chord.Chord)), 4)
+        self.assertEqual(len(s.flat.getElementsByClass(chord.Chord)), 5)
 
         # MIDI import makes measures, so we will have one 4/4 time sig
         self.assertEqual(len(s.flat.getElementsByClass(meter.TimeSignature)), 1)
@@ -1939,6 +1947,8 @@ class Test(unittest.TestCase):
             parse('nonexistent_path_ending_in_correct_extension.musicxml')
 
     def testParseURL(self):
+        from music21.humdrum.spineParser import HumdrumException
+
         urlBase = 'http://kern.ccarh.org/cgi-bin/ksdata?l=users/craig/classical/'
         url = urlBase + 'chopin/prelude&file=prelude28-20.krn&format=kern'
 
@@ -1947,6 +1957,18 @@ class Test(unittest.TestCase):
         s = parseURL(url)
         self.assertEqual(len(s.parts), 2)
 
+        # This file should have been written, above
+        destFp = Converter()._getDownloadFp(e.getRootTempDir(), '.krn', url)
+        # Hack garbage into it so that we can test whether or not forceSource works
+        with open(destFp, 'a') as fp:
+            fp.write('all sorts of garbage that Humdrum cannot parse')
+
+        with self.assertRaises(HumdrumException):
+            s = parseURL(url, forceSource=False)
+
+        s = parseURL(url, forceSource=True)
+        self.assertEqual(len(s.parts), 2)
+
 
 # ------------------------------------------------------------------------------
 # define presented order in documentation
diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py
index 9335a7d8a..d5d719c2b 100644
--- a/music21/converter/subConverters.py
+++ b/music21/converter/subConverters.py
@@ -236,6 +236,8 @@ class SubConverter:
         >>> tf = str(lpConverter.getTemporaryFile(subformats=['png']))
         >>> tf.endswith('.png')
         True
+        >>> import os  #_DOCS_HIDE
+        >>> os.remove(tf)  #_DOCS_HIDE
 
         Changed in v.6 -- returns pathlib.Path
         '''
@@ -316,7 +318,7 @@ class ConverterIPython(SubConverter):
     #     '''
     #     import random
     #     from music21.vexflow import toMusic21j
-    #     from IPython.display import HTML # @UnresolvedImport
+    #     from IPython.display import HTML
     #     vfp = toMusic21j.VexflowPickler()
     #     vfp.mode = 'jsonSplit'
     #     outputCode = vfp.fromObject(s)
@@ -845,7 +847,7 @@ class ConverterMusicXML(SubConverter):
     def findPNGfpFromXMLfp(self, xmlFilePath: Union[str, pathlib.Path]) -> str:
         '''
         Check whether total number of pngs is in 1-9, 10-99, or 100-999 range,
-        then return appropriate fp. Raises and exception if png fp does not exist.
+        then return appropriate fp. Raises an exception if png fp does not exist.
         '''
         xmlFilePath = str(xmlFilePath)  # not pathlib.
         path_without_extension = xmlFilePath[:-4]
@@ -1139,7 +1141,7 @@ class ConverterRomanText(SubConverter):
     def parseData(self, strData, number=None):
         from music21.romanText import rtObjects
         from music21.romanText import translate as romanTextTranslate
-        rtf = rtObjects.RTFile()  # not sure why -- @UndefinedVariable
+        rtf = rtObjects.RTFile()
         rtHandler = rtf.readstr(strData)
         if rtHandler.definesMovements():
             # this re-defines Score as an Opus
@@ -1433,7 +1435,7 @@ class Test(unittest.TestCase):
             xmlConverter1 = ConverterMusicXML()
             pngFp1 = xmlConverter1.findPNGfpFromXMLfp(xmlFp1)
             self.assertEqual(pngFp1, tempFp1)
-
+            os.remove(tempFp1)
 
     def testXMLtoPNGTooLong(self):
         '''
@@ -1446,6 +1448,7 @@ class Test(unittest.TestCase):
         tempFp += '-0000001.png'
         xmlConverter = ConverterMusicXML()
         self.assertRaises(SubConverterFileIOException, xmlConverter.findPNGfpFromXMLfp, xmlFp)
+        os.remove(tempFp)
 
     def testWriteMXL(self):
         from music21 import converter
@@ -1454,6 +1457,7 @@ class Test(unittest.TestCase):
         s = converter.parseData(testPrimitive.multiDigitEnding)
         mxlPath = s.write('mxl')
         self.assertTrue(str(mxlPath).endswith('.mxl'))
+        os.remove(mxlPath)
 
 
 class TestExternal(unittest.TestCase):  # pragma: no cover
diff --git a/music21/corpus/corpora.py b/music21/corpus/corpora.py
index c237fc515..f1e13d409 100644
--- a/music21/corpus/corpora.py
+++ b/music21/corpus/corpora.py
@@ -946,7 +946,7 @@ class LocalCorpus(Corpus):
 #         className = getattr(virtual, corpusName)
 #         if callable(className):
 #             obj = className()
-#             if isinstance(obj, virtual.VirtualWork):  # @UndefinedVariable
+#             if isinstance(obj, virtual.VirtualWork):
 #                 if obj.corpusPath is not None:
 #                     _virtualWorks.append(obj)
 #     del corpusName
diff --git a/music21/duration.py b/music21/duration.py
index 8c6318016..051d1f7a3 100644
--- a/music21/duration.py
+++ b/music21/duration.py
@@ -1821,8 +1821,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
         True
         '''
         newTuplet.frozen = True
-        self.tuplets = self.tuplets + (newTuplet,)
-        self.informClient()
+        self.tuplets = self._tuplets + (newTuplet,)
 
     def augmentOrDiminish(self, amountToScale, retainComponents=False):
         '''
@@ -2845,7 +2844,8 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
     @property
     def tuplets(self) -> Tuple[Tuplet, ...]:
         '''
-        return a tuple of Tuplet objects
+        Return a tuple of Tuplet objects.
+        Setting tuplets will inform the client (Note) that the duration has changed.
         '''
         if self._componentsNeedUpdating:
             self._updateComponents()
@@ -2856,6 +2856,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
         # environLocal.printDebug(['assigning tuplets in Duration', tupletTuple])
         self._tuplets = tuple(tupletTuple)
         self._quarterLengthNeedsUpdating = True
+        self.informClient()
 
     def aggregateTupletMultiplier(self) -> OffsetQL:
         '''
@@ -3590,7 +3591,7 @@ class Test(unittest.TestCase):
         self.assertEqual(str(d.components),
                          "(DurationTuple(type='eighth', dots=0, quarterLength=0.5),)")
         self.assertFalse(d._componentsNeedUpdating)
-        self.assertTrue(d._quarterLengthNeedsUpdating)
+        self.assertFalse(d._quarterLengthNeedsUpdating)
         self.assertEqual(repr(d.quarterLength), 'Fraction(1, 3)')
         self.assertEqual(str(unitSpec(d)), "(Fraction(1, 3), 'eighth', 0, 3, 2, 'eighth')")
 
diff --git a/music21/environment.py b/music21/environment.py
index 99398d703..f380ba9ed 100644
--- a/music21/environment.py
+++ b/music21/environment.py
@@ -622,11 +622,16 @@ class _EnvironmentCore:
 
         OMIT_FROM_DOCS
         >>> e = environment.Environment()
-        >>> isinstance(e.getTempFile(returnPathlib=False), str)
+        >>> tmp1 = e.getTempFile(returnPathlib=False)
+        >>> isinstance(tmp1, str)
         True
         >>> import pathlib
-        >>> isinstance(e.getTempFile(), pathlib.Path)
+        >>> tmp2 = e.getTempFile()
+        >>> isinstance(tmp2, pathlib.Path)
         True
+        >>> import os
+        >>> os.remove(tmp1)
+        >>> os.remove(tmp2)
         '''
         # get the root dir, which may be the user-specified dir
         rootDir = self.getRootTempDir()
@@ -1579,11 +1584,13 @@ class Test(unittest.TestCase):
             # Wipe out write, exec permissions on the default root dir
             os.chmod(e.getDefaultRootTempDir(), stat.S_IREAD)
             # Was the PermissionError caught and a new "music21-{user}" dir created?
-            self.assertIn('music21-' + getpass.getuser(), e.getTempFile(returnPathlib=False))
+            tmp = e.getTempFile(returnPathlib=False)
+            self.assertIn('music21-' + getpass.getuser(), tmp)
         finally:
             # Restore owner read/write/exec permissions and original path
             os.chmod(e.getDefaultRootTempDir(), stat.S_IRWXU)
             e['directoryScratch'] = oldScratchDir
+        os.remove(tmp)
 
 
 # -----------------------------------------------------------------------------
diff --git a/music21/expressions.py b/music21/expressions.py
index 9e74e2e7f..1da73f8a6 100644
--- a/music21/expressions.py
+++ b/music21/expressions.py
@@ -441,7 +441,7 @@ class GeneralMordent(Ornament):
         self.direction = ''  # up or down
         self.size = None  # interval.Interval (General, etc.) class
         self.quarterLength = 0.125  # 32nd note default
-        self.size = interval.Interval(2)
+        self.size = interval.GenericInterval(2)
 
     def realize(self, srcObj: 'music21.note.Note'):
         '''
@@ -504,15 +504,24 @@ class GeneralMordent(Ornament):
 
 class Mordent(GeneralMordent):
     '''
-    A normal Mordent.
+    A normal Mordent -- goes downwards and has a line through it.
+
+    Note that some computer terminology calls this one an inverted mordent, but this
+    is a modern term.  See Apel, _Harvard Dictionary of Music_, "Mordent"::
+
+        A musical ornament consisting of the alternation of the written note
+        with the note immediately below it.
+
 
     >>> m = expressions.Mordent()
     >>> m.direction
     'down'
     >>> m.size
-    <music21.interval.Interval M2>
-    '''
+    <music21.interval.GenericInterval 2>
 
+    Changed in v7: Mordent sizes are GenericIntervals -- as was originally
+    intended but programmed incorrectly.
+    '''
     def __init__(self):
         super().__init__()
         self.direction = 'down'  # up or down
@@ -553,13 +562,25 @@ class WholeStepMordent(Mordent):
 # ------------------------------------------------------------------------------
 class InvertedMordent(GeneralMordent):
     '''
-    An inverted Mordent.
+    An inverted Mordent -- goes upwards and has no line through it.
+
+    Note that some computer terminology calls this one a (normal) mordent, but this
+    is a modern term.    See Apel, _Harvard Dictionary of Music_,
+    "Inverted Mordent"::
+
+        An 18th-century ornament involving alternation of the
+        written note with the note immediately above it.
+
+    An inverted mordent has the size of a generic second, of some form.
 
     >>> m = expressions.InvertedMordent()
     >>> m.direction
     'up'
     >>> m.size
-    <music21.interval.Interval M2>
+    <music21.interval.GenericInterval 2>
+
+    Changed in v7: InvertedMordent sizes are GenericIntervals -- as was originally
+    intended but programmed incorrectly.
     '''
 
     def __init__(self):
@@ -774,6 +795,9 @@ class Schleifer(Ornament):
 
 # ------------------------------------------------------------------------------
 class Turn(Ornament):
+    '''
+    A turn or Gruppetto.
+    '''
     def __init__(self):
         super().__init__()
         self.size = interval.Interval('M2')
diff --git a/music21/features/__init__.py b/music21/features/__init__.py
index 23df684af..74e46372d 100644
--- a/music21/features/__init__.py
+++ b/music21/features/__init__.py
@@ -20,5 +20,5 @@ from music21.features import jSymbolic
 from music21.features import native
 
 # pylint: disable=redefined-builtin
-__doc__ = base.__doc__  # @ReservedAssignment @UndefinedVariable
+__doc__ = base.__doc__
 
diff --git a/music21/features/base.py b/music21/features/base.py
index 4f4679ddb..d94913202 100644
--- a/music21/features/base.py
+++ b/music21/features/base.py
@@ -538,7 +538,6 @@ class DataInstance:
     multiple commonly-used stream representations once, providing rapid processing.
     '''
     # pylint: disable=redefined-builtin
-
     def __init__(self, streamOrPath=None, id=None):  # @ReservedAssignment
         if isinstance(streamOrPath, stream.Stream):
             self.stream = streamOrPath
@@ -897,7 +896,6 @@ class DataSet:
             self.addData(d, cv, thisId)
 
     # pylint: disable=redefined-builtin
-
     def addData(self, dataOrStreamOrPath, classValue=None, id=None):  # @ReservedAssignment
         '''
         Add a Stream, DataInstance, MetadataEntry, or path (Posix or str)
@@ -1083,7 +1081,7 @@ class DataSet:
             raise DataSetException('no output format could be defined from file path '
                                    + f'{fp} or format {format}')
 
-        outputFormat.write(fp=fp, includeClassLabel=includeClassLabel)
+        return outputFormat.write(fp=fp, includeClassLabel=includeClassLabel)
 
 
 def _dataSetParallelSubprocess(dataInstance, failFast):
@@ -1424,9 +1422,14 @@ class Test(unittest.TestCase):
             'Unique_Note_Quarter_Lengths,Most_Common_Note_Quarter_Length,'
             'Range_of_Note_Quarter_Lengths,Composer//3,1.0,1.5,Bach//8,0.5,3.75,Corelli')
 
-        ds.write(format='tab')
-        ds.write(format='csv')
-        ds.write(format='arff')
+        fp1 = ds.write(format='tab')
+        fp2 = ds.write(format='csv')
+        # Also test providing fp
+        fp3 = environLocal.getTempFile(suffix='.arff')
+        ds.write(fp=fp3, format='arff')
+
+        for fp in (fp1, fp2, fp3):
+            os.remove(fp)
 
     def testFeatureFail(self):
         from music21 import features
@@ -1809,7 +1812,7 @@ class Test(unittest.TestCase):
         from music21 import features
 
         # Need explicit import for pickling within the testSingleCoreAll context
-        from music21.features.base import _pickleFunctionNumPitches  # @UnresolvedImport
+        from music21.features.base import _pickleFunctionNumPitches
         import textwrap
 
         self.maxDiff = None
diff --git a/music21/features/native.py b/music21/features/native.py
index 8b44b9f8b..9f83bcdee 100644
--- a/music21/features/native.py
+++ b/music21/features/native.py
@@ -17,7 +17,7 @@ import math
 from typing import Optional
 
 from urllib.request import Request, urlopen
-from urllib.parse import urlencode  # @UnresolvedImport @Reimport
+from urllib.parse import urlencode
 
 
 from music21.features import base as featuresModule
diff --git a/music21/graph/plot.py b/music21/graph/plot.py
index 0e9f028ae..3ed0ad8e1 100644
--- a/music21/graph/plot.py
+++ b/music21/graph/plot.py
@@ -1543,7 +1543,8 @@ class TestExternal(unittest.TestCase):  # pragma: no cover
             (ScatterPitchClassQuarterLength, None, None),
             (ScatterPitchClassOffset, None, None),
             (ScatterPitchSpaceDynamicSymbol,
-             corpus.getWork('schumann/opus41no1', 2), 'Schumann Opus 41 No 1'),
+             corpus.getWork('schumann/opus41no1', 2),
+             'Schumann Opus 41 No 1'),
 
             # offset based horizontal
             (HorizontalBarPitchSpaceOffset, None, None),
@@ -1552,12 +1553,14 @@ class TestExternal(unittest.TestCase):  # pragma: no cover
             (ScatterWeightedPitchSpaceQuarterLength, None, None),
             (ScatterWeightedPitchClassQuarterLength, None, None),
             (ScatterWeightedPitchSpaceDynamicSymbol,
-             corpus.getWork('schumann/opus41no1', 2), 'Schumann Opus 41 No 1'),
+             corpus.getWork('schumann/opus41no1', 2),
+             'Schumann Opus 41 No 1'),
 
 
             # 3d graphs
             (Plot3DBarsPitchSpaceQuarterLength,
-             testFiles.mozartTrioK581Excerpt, 'Mozart Trio K581 Excerpt'),  # @UndefinedVariable
+             testFiles.mozartTrioK581Excerpt,
+             'Mozart Trio K581 Excerpt'),
 
             (WindowedKey, corpus.getWork('bach/bwv66.6.xml'), 'Bach BWV 66.6'),
             (WindowedAmbitus, corpus.getWork('bach/bwv66.6.xml'), 'Bach BWV 66.6'),
diff --git a/music21/graph/primitives.py b/music21/graph/primitives.py
index 526448ad9..4fc84ad00 100644
--- a/music21/graph/primitives.py
+++ b/music21/graph/primitives.py
@@ -1785,7 +1785,7 @@ class TestExternal(unittest.TestCase):  # pragma: no cover
         g.process()
 
     def testGraphNetworkxGraph(self):
-        extm = getExtendedModules()  # @UnusedVariable
+        extm = getExtendedModules()
 
         if extm.networkx is not None:  # pragma: no cover
             b = GraphNetworkxGraph(doneAction=None)
diff --git a/music21/graph/utilities.py b/music21/graph/utilities.py
index aa90bdc7c..fec3c8c80 100644
--- a/music21/graph/utilities.py
+++ b/music21/graph/utilities.py
@@ -43,12 +43,12 @@ def getExtendedModules():
     if 'matplotlib' in _missingImport:
         raise GraphException(
             'could not find matplotlib, graphing is not allowed')  # pragma: no cover
-    import matplotlib  # @UnresolvedImport
+    import matplotlib
     # backend can be configured from config file, matplotlibrc,
     # but an early test broke all processing
     # matplotlib.use('WXAgg')
     try:
-        from mpl_toolkits.mplot3d import Axes3D  # @UnresolvedImport
+        from mpl_toolkits.mplot3d import Axes3D
     except ImportError:  # pragma: no cover
         Axes3D = None
         environLocal.warn(
@@ -56,13 +56,14 @@ def getExtendedModules():
             + 'old version of six.py (< 1.9.0) on your system somewhere'
         )
 
-    from matplotlib import collections  # @UnresolvedImport
-    from matplotlib import patches  # @UnresolvedImport
+    from matplotlib import collections
+    from matplotlib import patches
 
     # from matplotlib.colors import colorConverter
-    import matplotlib.pyplot as plt  # @UnresolvedImport
+    import matplotlib.pyplot as plt
 
     try:
+        # noinspection PyPackageRequirements
         import networkx
     except ImportError:  # pragma: no cover
         networkx = None  # use for testing
diff --git a/music21/harmony.py b/music21/harmony.py
index 20345c1bc..75e208a1a 100644
--- a/music21/harmony.py
+++ b/music21/harmony.py
@@ -1387,8 +1387,8 @@ class ChordSymbol(Harmony):
     chords, by default appear as chord symbols in a score and have duration of
     0.
 
-    To obtain the chord representation of the in the score, change the
-    :attr:`music21.harmony.ChordSymbol.writeAsChord` to True. Unless otherwise
+    To obtain the chord representation of the `ChordSymbol` in the score, change
+    :attr:`~music21.harmony.Harmony.writeAsChord` to True. Unless otherwise
     specified, the duration of this chord object will become 1.0. If you have a
     leadsheet, run :meth:`music21.harmony.realizeChordSymbolDurations` on the
     stream to assign the correct (according to offsets) duration to each
@@ -2204,7 +2204,7 @@ class ChordSymbol(Harmony):
             'Tristan',
             'augmented-seventh',
             'diminished-seventh',
-            'dominant',
+            'dominant-seventh',
             'half-diminished',
             'major-minor',
             'major-seventh',
@@ -2708,11 +2708,7 @@ class Test(unittest.TestCase):
           </harmony>
           """
         figure = 'A7/G'
-        pitches = ('G2', 'A3', 'C#4', 'E4', 'G4')
-        # TODO: Get rid of the extra G once we do something about ChordSymbol construction,
-        # since currently bass() is called before _updatePitches()
-        # and each of them is creating a G
-        # https://github.com/cuthbertLab/music21/issues/793
+        pitches = ('G2', 'A2', 'C#3', 'E3')
 
         self.runTestOnChord(xmlString, figure, pitches)
 
diff --git a/music21/humdrum/instruments.py b/music21/humdrum/instruments.py
index f968fc86b..ed25d8703 100644
--- a/music21/humdrum/instruments.py
+++ b/music21/humdrum/instruments.py
@@ -264,14 +264,14 @@ def fromHumdrumInstrument(hdInst):
 class Test(unittest.TestCase):
 
     def testClasses(self):
-        from music21 import instrument  # @UnusedImport  # wrong -- is used
+        from music21 import instrument
 
         for x in humdrumInstrumentClassToInstrument:
             i = humdrumInstrumentClassToInstrument[x]
             self.assertNotEqual(getattr(instrument, i)().instrumentName, None)
 
     def testIndividuals(self):
-        from music21 import instrument  # @UnusedImport  # wrong -- is used
+        from music21 import instrument
 
         for x in humdrumInstruments:
             i = humdrumInstruments[x]
diff --git a/music21/ipython21/__init__.py b/music21/ipython21/__init__.py
index f18dfceaa..bc9cb54c3 100644
--- a/music21/ipython21/__init__.py
+++ b/music21/ipython21/__init__.py
@@ -36,8 +36,8 @@ def loadNoMagic():
 
 
 # if we are imported in an IPython environment, then load magic after half a second
-if common.runningUnderIPython():  # @UndefinedVariable
+if common.runningUnderIPython():
     from threading import Timer
     t = Timer(2, loadNoMagic)
     t.start()
-    # ipython21.load_no_magic() # @UndefinedVariable
+    # ipython21.load_no_magic()
diff --git a/music21/ipython21/ipExtension.py b/music21/ipython21/ipExtension.py
index 6e791a4b6..69b11a618 100644
--- a/music21/ipython21/ipExtension.py
+++ b/music21/ipython21/ipExtension.py
@@ -11,9 +11,9 @@ def load_ipython_extension(ip):
 
     MAY 2017: everything happens in converter.subConverters
     '''
-#     pngFormatter = ip.display_formatter.formatters['image/png']
-#     pngFormatter.for_type(music21.ipython21.objects.IPythonPNGObject,
-#                           music21.ipython21.objects.IPythonPNGObject.getData)
+    # pngFormatter = ip.display_formatter.formatters['image/png']
+    # pngFormatter.for_type(music21.ipython21.objects.IPythonPNGObject,
+    #                       music21.ipython21.objects.IPythonPNGObject.getData)
     try:
         from matplotlib import pyplot as plt
         plt.ion()
diff --git a/music21/lily/lilyObjects.py b/music21/lily/lilyObjects.py
index a909c240a..b5793941e 100644
--- a/music21/lily/lilyObjects.py
+++ b/music21/lily/lilyObjects.py
@@ -1283,7 +1283,6 @@ class LyPrefixCompositeMusic(LyObject):
                        | re_rhythmed_music
     '''
     # pylint: disable=redefined-builtin
-
     def __init__(self, type=None, genericPrefixMusicScm=None,  # @ReservedAssignment
                  simpleString=None, optionalId=None, optionalContextMod=None,
                  music=None, fraction=None, repeatedMusic=None,
diff --git a/music21/lily/translate.py b/music21/lily/translate.py
index 951430159..a7e4744c9 100644
--- a/music21/lily/translate.py
+++ b/music21/lily/translate.py
@@ -2494,7 +2494,7 @@ class LilypondConverter:
             from PIL import Image, ImageOps
             # noinspection PyBroadException
             try:
-                lilyImage = Image.open(str(lilyFile))  # @UndefinedVariable
+                lilyImage = Image.open(str(lilyFile))
                 lilyImage2 = ImageOps.expand(lilyImage, 10, 'white')
                 lilyImage2.save(str(lilyFile))
             except Exception:  # pylint: disable=broad-except
diff --git a/music21/metadata/bundles.py b/music21/metadata/bundles.py
index 3b69b4b21..53c735502 100644
--- a/music21/metadata/bundles.py
+++ b/music21/metadata/bundles.py
@@ -1347,8 +1347,9 @@ class MetadataBundle(prebase.ProtoM21Object):
         True
 
         >>> import os
-        >>> import tempfile
-        >>> tempFilePath = tempfile.mkstemp()[1]
+        >>> from music21 import environment
+        >>> e = environment.Environment()
+        >>> tempFilePath = e.getTempFile()
         >>> bachBundle.write(filePath=tempFilePath)
         <music21.metadata.bundles.MetadataBundle {363 entries}>
         >>> os.remove(tempFilePath)
diff --git a/music21/metadata/caching.py b/music21/metadata/caching.py
index 859b45494..86fd7339d 100644
--- a/music21/metadata/caching.py
+++ b/music21/metadata/caching.py
@@ -327,7 +327,7 @@ class JobProcessor:
         jobs is a list of :class:`~music21.metadata.MetadataCachingJob` objects.
 
         '''
-        processCount = processCount or common.cpus()  # @UndefinedVariable
+        processCount = processCount or common.cpus()
         if processCount < 1:
             processCount = 1
         remainingJobs = len(jobs)
@@ -337,8 +337,8 @@ class JobProcessor:
         environLocal.printDebug(
             f'Processing {remainingJobs} jobs in parallel, with {processCount} processes.')
         results = []
-        job_queue = multiprocessing.JoinableQueue()  # @UndefinedVariable
-        result_queue = multiprocessing.Queue()  # @UndefinedVariable
+        job_queue = multiprocessing.JoinableQueue()
+        result_queue = multiprocessing.Queue()
         workers = [WorkerProcess(job_queue, result_queue)
                    for _ in range(processCount)]
         for worker in workers:
diff --git a/music21/midi/__init__.py b/music21/midi/__init__.py
index 32fdce403..70e0e2b64 100644
--- a/music21/midi/__init__.py
+++ b/music21/midi/__init__.py
@@ -38,7 +38,7 @@ import os
 import string
 import struct
 import sys
-import unicodedata  # @UnresolvedImport
+import unicodedata
 import unittest
 from typing import Optional, Union, Tuple
 
diff --git a/music21/midi/translate.py b/music21/midi/translate.py
index e3857857b..56e5b97c6 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -1742,7 +1742,7 @@ def midiTrackToStream(
     >>> p
     <music21.stream.Part ...>
     >>> len(p.flat.notesAndRests)
-    13
+    14
     >>> p.flat.notes[0].pitch.midi
     36
     >>> p.flat.notes[0].volume.velocity
@@ -1770,7 +1770,8 @@ def midiTrackToStream(
         {1.0} <music21.note.Rest rest>
         {2.5} <music21.chord.Chord F#3 A4 C#5>
     {12.0} <music21.stream.Measure 4 offset=12.0>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.chord.Chord F#3 A4 C#5>
+        {2.5} <music21.note.Rest rest>
         {4.0} <music21.bar.Barline type=final>
     '''
     # environLocal.printDebug(['midiTrackToStream(): got midi track: events',
@@ -1921,8 +1922,9 @@ def midiTrackToStream(
     for m in s.getElementsByClass(stream.Measure):
         if voicesRequired:
             m.makeVoices(inPlace=True, fillGaps=True)
-        # always need to fill gaps, as rests are not found in any other way
-        m.makeRests(inPlace=True, fillGaps=True, timeRangeFromBarDuration=True)
+    s.makeTies(inPlace=True)
+    # always need to fill gaps, as rests are not found in any other way
+    s.makeRests(inPlace=True, fillGaps=True, timeRangeFromBarDuration=True)
     return s
 
 
@@ -2614,7 +2616,7 @@ def midiFileToStream(
     >>> s
     <music21.stream.Score ...>
     >>> len(s.flat.notesAndRests)
-    13
+    14
     '''
     # environLocal.printDebug(['got midi file: tracks:', len(mf.tracks)])
     if inputM21 is None:
@@ -3534,7 +3536,7 @@ class Test(unittest.TestCase):
         # a simple file created in athenacl
         s = converter.parse(fp)
         # s.show('t')
-        self.assertEqual(len(s.flat.getElementsByClass('Chord')), 4)
+        self.assertEqual(len(s.flat.getElementsByClass('Chord')), 5)
 
     def testMidiEventsImported(self):
         self.maxDiff = None
@@ -3685,6 +3687,28 @@ class Test(unittest.TestCase):
         self.assertEqual(
             len(inn.parts[1].measure(3).voices.last().getElementsByClass('Rest')), 1)
 
+    def testRestsMadeInMeasures(self):
+        from music21 import converter
+
+        fp = common.getSourceFilePath() / 'midi' / 'testPrimitive' / 'test17.mid'
+        inn = converter.parse(fp)
+        pianoLH = inn.parts.last()
+        m1 = pianoLH.measure(1)  # quarter note, quarter note, quarter rest
+        m2 = pianoLH.measure(2)
+        self.assertEqual(len(m1.notesAndRests), 3)
+        self.assertEqual(len(m1.notes), 2)
+        self.assertEqual(m1.duration.quarterLength, 3.0)
+        self.assertEqual(pianoLH.elementOffset(m2), 3.0)
+
+        for part in inn.parts:
+            with self.subTest(part=part):
+                self.assertEqual(
+                    sum(m.barDuration.quarterLength for m in part.getElementsByClass(
+                        stream.Measure)
+                        ),
+                    part.duration.quarterLength
+                )
+
 
 # ------------------------------------------------------------------------------
 _DOC_ORDER = [streamToMidiFile, midiFileToStream]
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index 04ee237aa..ea2a14abf 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -1353,6 +1353,8 @@ class ScoreExporter(XMLExporterBase, PartStaffExporterMixin):
 
         self.partExporterList: List['PartExporter'] = []
 
+        self.joinedGroups: List['StaffGroup'] = []
+
         self.instrumentList = []
         self.midiChannelList = []
 
@@ -1929,6 +1931,20 @@ class ScoreExporter(XMLExporterBase, PartStaffExporterMixin):
           <score-part id="P4">...
           <part-group number="1" type="stop" />
         </part-list>
+
+        And with a piano staff, to observe that only one <part> tag has both "start" and "stop":
+
+        >>> cpe = corpus.parse('cpebach')
+        >>> SX = musicxml.m21ToXml.ScoreExporter(cpe)
+        >>> SX.scorePreliminaries()
+        >>> SX.parsePartlikeScore()
+
+        >>> mxPartList = SX.setPartList()
+        >>> SX.dump(mxPartList)
+        <part-list>
+          <part-group number="1" type="start">...
+          <part-group number="1" type="stop" />
+        </part-list>
         '''
 
         spannerBundle = self.spannerBundle
@@ -1959,7 +1975,10 @@ class ScoreExporter(XMLExporterBase, PartStaffExporterMixin):
             # check for last
             activeIndex = None
             for sg in staffGroups:
-                if sg.isLast(p):
+                # Handle last part in the StaffGroup
+                # as well as the first part in the StaffGroup if it was joined to the others
+                # In that latter case, we need the <part-group type="stop" /> on the FIRST m21 part
+                if (sg.isLast(p) or (sg in self.joinedGroups and sg.isFirst(p))):
                     # find the spanner in the dictionary already-assigned
                     for key, value in partGroupIndexRef.items():
                         if value is sg:
@@ -5808,11 +5827,11 @@ class MeasureExporter(XMLExporterBase):
             return mxTime
 
         # always get a flat version to display any subdivisions created
-        fList = [(mt.numerator, mt.denominator) for mt in ts.displaySequence.flat]
+        fList = tuple((mt.numerator, mt.denominator) for mt in ts.displaySequence.flat)
         if ts.summedNumerator:
             # this will try to reduce any common denominators into
             # a common group
-            fList = meter.fractionToSlashMixed(fList)
+            fList = meter.tools.fractionToSlashMixed(fList)
 
         for n, d in fList:
             mxBeats = SubElement(mxTime, 'beats')
@@ -6431,6 +6450,13 @@ class Test(unittest.TestCase):
         endings = x.findall('.//ending')
         self.assertEqual([e.get('number') for e in endings], ['1,2', '1,2', '3', '3'])
 
+        # Check templates also
+        template = s.template()
+        template.makeNotation(inPlace=True)  # not essential, but since getET() skips this
+        x = self.getET(template)
+        endings = x.findall('.//ending')
+        self.assertEqual([e.get('number') for e in endings], ['1,2', '1,2', '3', '3'])
+
         # m21 represents lack of bracket numbers as 0; musicxml uses ''
         s.parts[0].getElementsByClass('RepeatBracket').first().number = 0
         x = self.getET(s)
diff --git a/music21/musicxml/partStaffExporter.py b/music21/musicxml/partStaffExporter.py
index 45b85a1f5..33e0bc05b 100644
--- a/music21/musicxml/partStaffExporter.py
+++ b/music21/musicxml/partStaffExporter.py
@@ -131,7 +131,8 @@ class PartStaffExporterMixin:
         >>> len(staffTags)
         2
         '''
-        for group in self.joinableGroups():
+        self.joinedGroups = self.joinableGroups()
+        for group in self.joinedGroups:
             self.addStaffTagsMultiStaffParts(group)
             self.movePartStaffMeasureContents(group)
             self.setEarliestAttributesAndClefsPartStaff(group)
@@ -283,13 +284,6 @@ class PartStaffExporterMixin:
             <staff>2</staff>
           </note>
         </measure>
-
-        Fails if attempted a second time:
-
-        >>> root = SX.parse()
-        Traceback (most recent call last):
-        music21.musicxml.xmlObjects.MusicXMLExportException:
-            In part (MusicXML Part), measure (1): Attempted to create a second <staff> tag
         '''
         initialPartStaffRoot: Optional[Element] = None
         for i, ps in enumerate(group):
@@ -595,10 +589,6 @@ class PartStaffExporterMixin:
         by a single :class:`~music21.musicxml.m21ToXml.PartExporter`, remove any
         obsolete `PartExporter` from `self.partExporterList`.
 
-        In addition, remove any obsolete `PartStaff` from the `StaffGroup`
-        (in the deepcopied stream used for exporting) to ensure <part-group type="stop" />
-        is written.
-
         Called by :meth:`joinPartStaffs`
 
         >>> from music21.musicxml import testPrimitive
@@ -611,9 +601,6 @@ class PartStaffExporterMixin:
         >>> SX.postPartProcess()
         >>> len(SX.partExporterList)
         1
-        >>> partGroupStop = SX.xmlRoot.findall('.//part-group')[1]
-        >>> SX.dump(partGroupStop)
-        <part-group number="1" type="stop" />
         '''
         for ps in group[1:]:
             partStaffRoot: Element = self.getRootForPartStaff(ps)
@@ -621,8 +608,6 @@ class PartStaffExporterMixin:
             # noinspection PyAttributeOutsideInit
             self.partExporterList = [pex for pex in self.partExporterList
                                         if pex.xmlRoot != partStaffRoot]
-            # Replace PartStaff in StaffGroup -- ensures <part-group number="1" type="stop" />
-            group.replaceSpannedElement(ps, group.getFirst())
 
     @staticmethod
     def moveMeasureContents(measure: Element, otherMeasure: Element, staffNumber: int):
@@ -945,6 +930,18 @@ class Test(unittest.TestCase):
         SX.parsePartlikeScore()
         self.assertEqual(len(SX.joinableGroups()), 1)
 
+    def testJoinPartStaffsAgain(self):
+        '''
+        Regression test for side effects on the stream passed to ScoreExporter
+        preventing it from being written out again.
+        '''
+        from music21 import corpus
+        from music21.musicxml.m21ToXml import ScoreExporter
+        b = corpus.parse('cpebach')
+        SX = ScoreExporter(b)
+        SX.parse()
+        SX.parse()
+
 
 if __name__ == '__main__':
     import music21
diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py
index 781f39705..7601297c7 100644
--- a/music21/musicxml/xmlToM21.py
+++ b/music21/musicxml/xmlToM21.py
@@ -1984,6 +1984,17 @@ class PartParser(XMLParserBase):
 
         sets self.lastMeasureWasShort to True or False if it is an incomplete measure
         that is not a pickup.
+
+        >>> m = stream.Measure([meter.TimeSignature('4/4'), harmony.ChordSymbol('C7')])
+        >>> m.highestTime
+        0.0
+        >>> PP = musicxml.xmlToM21.PartParser()
+        >>> PP.setLastMeasureInfo(m)
+        >>> PP.adjustTimeAttributesFromMeasure(m)
+        >>> m.highestTime
+        4.0
+        >>> PP.lastMeasureWasShort
+        False
         '''
         # note: we cannot assume that the time signature properly
         # describes the offsets w/n this bar. need to look at
@@ -1999,7 +2010,9 @@ class PartParser(XMLParserBase):
         if mHighestTime >= lastTimeSignatureQuarterLength:
             mOffsetShift = mHighestTime
 
-        elif mHighestTime == 0.0 and not m.recurse().notesAndRests:
+        elif (mHighestTime == 0.0
+              and not m.recurse().notesAndRests.getElementsNotOfClass('Harmony')
+              ):
             # this routine fixes a bug in PDFtoMusic and other MusicXML writers
             # that omit empty rests in a Measure.  It is a very quick test if
             # the measure has any notes.  Slower if it does not.
@@ -2007,6 +2020,7 @@ class PartParser(XMLParserBase):
             r.duration.quarterLength = lastTimeSignatureQuarterLength
             m.insert(0.0, r)
             mOffsetShift = lastTimeSignatureQuarterLength
+            self.lastMeasureWasShort = False
 
         else:  # use time signature
             # for the first measure, this may be a pickup
@@ -3808,7 +3822,7 @@ class MeasureParser(XMLParserBase):
         # add a reference of this note to this spanner
         if target is not None:
             su.addSpannedElements(target)
-        # environLocal.printDebug(['adding n', n, id(n), 'su.getSpannedElements',
+        # environLocal.printDebug(['adding n', target, id(target), 'su.getSpannedElements',
         #     su.getSpannedElements(), su.getSpannedElementIds()])
         if mxObj.get('type') == 'stop':
             su.completeStatus = True
diff --git a/music21/pitch.py b/music21/pitch.py
index 99738c58e..de3d437c8 100644
--- a/music21/pitch.py
+++ b/music21/pitch.py
@@ -2913,10 +2913,7 @@ class Pitch(prebase.ProtoM21Object):
         >>> a
         <music21.pitch.Pitch C#3(+20c)>
         '''
-        pc = round(self.ps) % 12
-        if pc == 12:
-            pc = 0
-        return pc
+        return round(self.ps) % 12
 
     @pitchClass.setter
     def pitchClass(self, value: Union[str, int]):
diff --git a/music21/roman.py b/music21/roman.py
index b67d08503..18f054c99 100644
--- a/music21/roman.py
+++ b/music21/roman.py
@@ -19,6 +19,9 @@ import copy
 import re
 from typing import Union, Optional, List, Tuple
 
+# when python 3.7 is removed from support:
+# from typing import Literal
+
 from collections import namedtuple
 
 from music21 import environment
@@ -75,6 +78,7 @@ def _getKeyFromCache(keyStr: str) -> key.Key:
     return keyObj
 
 
+# figure shorthands for all modes.
 figureShorthands = {
     '53': '',
     '3': '',
@@ -92,12 +96,22 @@ figureShorthands = {
     '6b53': '6b5',
     '643': '43',
     '642': '42',
-    'bb7b5b3': 'o7',
-    'bb7b53': 'o7',
     # '6b5bb3': 'o65',
+    'bb7b5b3': 'o7',
     'b7b5b3': 'ø7',
+    'bb7b53': 'o7',
+    'b7b53': 'ø7',
+}
+
+figureShorthandsMode = {
+    'major': {
+    },
+    'minor': {
+    }
 }
 
+
+# this is sort of a crock...  :-)  but it's very helpful.
 functionalityScores = {
     'I': 100,
     'i': 90,
@@ -130,11 +144,15 @@ functionalityScores = {
     'iio': 37,  # common in Minor
     'iio42': 36,
     'bII6': 35,  # Neapolitan
+    'It6': 34,
+    'Ger65': 33,
     'iio43': 32,
     'iio65': 31,
+    'Fr43': 30,
     '#vio': 28,
     '#vio6': 27,
     'III': 22,
+    'Sw43': 21,
     'v': 20,
     'VII': 19,
     'VII7': 18,
@@ -284,6 +302,7 @@ def postFigureFromChordAndKey(chordObj, keyObj=None):
     ...     )
     '6b5'
 
+
     We reduce common omissions from seventh chords to be '7' instead
     of '75', '73', etc.
 
@@ -294,6 +313,11 @@ def postFigureFromChordAndKey(chordObj, keyObj=None):
     'o7'
 
     Returns string.
+
+    OMIT_FROM_DOCS
+
+    Fails on German Augmented 6th chords in root position.  Calls them
+    half-diminished chords.
     '''
     if keyObj is None:
         keyObj = key.Key(chordObj.root())
@@ -351,7 +375,10 @@ def postFigureFromChordAndKey(chordObj, keyObj=None):
                 allFigureStringList.append(figureString)
 
     allFigureString = ''.join(allFigureStringList)
-    if allFigureString in figureShorthands:
+    key_mode = keyObj.mode
+    if key_mode in figureShorthandsMode and allFigureString in figureShorthandsMode[key_mode]:
+        allFigureString = figureShorthandsMode[allFigureString]
+    elif allFigureString in figureShorthands:
         allFigureString = figureShorthands[allFigureString]
 
     # simplify common omissions from 7th chords
@@ -384,14 +411,28 @@ def figureTuples(chordObject, keyObject):
      ChordFigureTuple(aboveBass=3, alter=-1.0, prefix='b', pitch=<music21.pitch.Pitch A-3>),
      ChordFigureTuple(aboveBass=5, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch C#4>)]
 
+    In c-minor, the A- is a normal note, so the prefix is '' not 'b'.  The natural minor is used
+    exclusively.
+
+    >>> roman.figureTuples(
+    ...     chord.Chord(['F#2', 'D3', 'A-3', 'C#4']),
+    ...     key.Key('c'),
+    ...     )
+    [ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch F#2>),
+     ChordFigureTuple(aboveBass=6, alter=0.0, prefix='', pitch=<music21.pitch.Pitch D3>),
+     ChordFigureTuple(aboveBass=3, alter=0.0, prefix='', pitch=<music21.pitch.Pitch A-3>),
+     ChordFigureTuple(aboveBass=5, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch C#4>)]
+
+    A C dominant-seventh chord in c minor alters the bass but not the 7th degree
+
     >>> roman.figureTuples(
     ...     chord.Chord(['E3', 'C4', 'G4', 'B-5']),
-    ...     key.Key('C'),
+    ...     key.Key('c'),
     ...     )
-    [ChordFigureTuple(aboveBass=1, alter=0.0, prefix='', pitch=<music21.pitch.Pitch E3>),
+    [ChordFigureTuple(aboveBass=1, alter=1.0, prefix='#', pitch=<music21.pitch.Pitch E3>),
      ChordFigureTuple(aboveBass=6, alter=0.0, prefix='', pitch=<music21.pitch.Pitch C4>),
      ChordFigureTuple(aboveBass=3, alter=0.0, prefix='', pitch=<music21.pitch.Pitch G4>),
-     ChordFigureTuple(aboveBass=5, alter=-1.0, prefix='b', pitch=<music21.pitch.Pitch B-5>)]
+     ChordFigureTuple(aboveBass=5, alter=0.0, prefix='', pitch=<music21.pitch.Pitch B-5>)]
 
     >>> roman.figureTuples(
     ...     chord.Chord(['C4', 'E4', 'G4', 'C#4']),
@@ -407,14 +448,18 @@ def figureTuples(chordObject, keyObject):
     for thisPitch in chordObject.pitches:
         shortTuple = figureTupleSolo(thisPitch, keyObject, bass)
         appendTuple = ChordFigureTuple(shortTuple.aboveBass,
-                                        shortTuple.alter,
-                                        shortTuple.prefix,
-                                        thisPitch)
+                                       shortTuple.alter,
+                                       shortTuple.prefix,
+                                       thisPitch)
         result.append(appendTuple)
     return result
 
 
-def figureTupleSolo(pitchObj, keyObj, bass):
+def figureTupleSolo(
+    pitchObj: pitch.Pitch,
+    keyObj: key.Key,
+    bass: pitch.Pitch
+) -> FigureTuple:
     '''
     Return a single tuple for a pitch and key showing the interval above
     the bass, its alteration from a step in the given key, an alteration
@@ -422,7 +467,6 @@ def figureTupleSolo(pitchObj, keyObj, bass):
 
     For instance, in C major, an A-3 above an F# bass would be:
 
-    >>> from music21 import roman
     >>> roman.figureTupleSolo(
     ...     pitch.Pitch('A-3'),
     ...     key.Key('C'),
@@ -430,6 +474,27 @@ def figureTupleSolo(pitchObj, keyObj, bass):
     ...     )
     FigureTuple(aboveBass=3, alter=-1.0, prefix='b')
 
+    These figures can be more complex in minor, so this is a good reference, showing
+    that natural minor is always used.
+
+    >>> c = key.Key('c')
+    >>> c_as_bass = pitch.Pitch('C3')
+    >>> for name in ('E--', 'E-', 'E', 'E#', 'A--', 'A-', 'A', 'A#', 'B--', 'B-', 'B', 'B#'):
+    ...     ft = roman.figureTupleSolo(pitch.Pitch(name + '4'), c, c_as_bass)
+    ...     print(f'{name:4s} {ft}')
+    E--  FigureTuple(aboveBass=3, alter=-1.0, prefix='b')
+    E-   FigureTuple(aboveBass=3, alter=0.0, prefix='')
+    E    FigureTuple(aboveBass=3, alter=1.0, prefix='#')
+    E#   FigureTuple(aboveBass=3, alter=2.0, prefix='##')
+    A--  FigureTuple(aboveBass=6, alter=-1.0, prefix='b')
+    A-   FigureTuple(aboveBass=6, alter=0.0, prefix='')
+    A    FigureTuple(aboveBass=6, alter=1.0, prefix='#')
+    A#   FigureTuple(aboveBass=6, alter=2.0, prefix='##')
+    B--  FigureTuple(aboveBass=7, alter=-1.0, prefix='b')
+    B-   FigureTuple(aboveBass=7, alter=0.0, prefix='')
+    B    FigureTuple(aboveBass=7, alter=1.0, prefix='#')
+    B#   FigureTuple(aboveBass=7, alter=2.0, prefix='##')
+
     Returns a namedtuple called a FigureTuple.
     '''
     unused_scaleStep, scaleAccidental = keyObj.getScaleDegreeAndAccidentalFromPitch(pitchObj)
@@ -764,6 +829,7 @@ def romanNumeralFromChord(
     <music21.roman.RomanNumeral Sw43 in g minor>
 
 
+
     With correct key context:
 
     >>> roman.romanNumeralFromChord(
@@ -781,6 +847,10 @@ def romanNumeralFromChord(
     <music21.roman.RomanNumeral #io6b3 in C major>
 
 
+    Augmented 6th chords in other inversions do not currently find correct roman numerals
+
+
+
     Changed in v7 -- i7 is given for a tonic or subdominant minor-seventh chord in major:
 
     >>> roman.romanNumeralFromChord(
@@ -855,17 +925,59 @@ def romanNumeralFromChord(
     # <music21.roman.RomanNumeral IV7 in c minor>
     # <music21.roman.RomanNumeral IV#75#3 in c minor>
     '''
+
+    # use these when we know the key...  don't we need to know the mode?
     aug6subs = {
         '#ivo6b3': 'It6',
+        '#ivob64': 'It64',  # minor only
+        '#ivobb64': 'It64',  # major only
+        '#ivob5b3': 'It53',  # minor only
+        '#ivob5bb3': 'It53',  # major only
+
         'IIø#643': 'Fr43',
+        'IIø75#3': 'Fr7',  # in minor
+        'IIø7b5#3': 'Fr7',  # in major
+        'IIø6#42': 'Fr42',  # in minor
+        'IIøb6#42': 'Fr42',  # in major
+        'IIø65': 'Fr65',  # in minor seems wrong...
+        'IIø65b3': 'Fr65',  # in major
+
         '#ii64b3': 'Sw43',
-        '#ivo6bb5b3': 'Ger65',
+        '#iiø7': 'Sw7',  # minor; is wrong
+        '#iib7bb53': 'Sw7',  # major
+        '#iib642': 'Sw42',  # minor
+        '#iibb642': 'Sw42',  # major
+        '#ii6b5b3': 'Sw65',  # minor
+        '#ii6b5bb3': 'Sw65',  # major
+
+        '#ivo6b5b3': 'Ger65',  # in minor
+        '#ivo6bb5b3': 'Ger65',  # in major
+        '#ivob64b3': 'Ger43',  # in minor
+        '#ivobb64bb3': 'Ger43',  # in major
+        '#ivob6b42': 'Ger42',  # in minor
+        '#ivob6bb42': 'Ger42',  # in major
+        '#ivø7': 'Ger7',  # in minor -- seems wrong
+        '#ivobb7b5bb3': 'Ger7',  # in major
     }
     aug6NoKeyObjectSubs = {
         'io6b3': 'It6',
+        'iob64': 'It64',
+        'iob5b3': 'It53',
+
         'Iø64b3': 'Fr43',
+        'Iøb7b53': 'Fr7',
+        'Iøb642': 'Fr42',
+        'Iø6b5b3': 'Fr65',
+
         'i64b3': 'Sw43',
+        'ib7bb53': 'Sw7',
+        'ibb642': 'Sw42',
+        'i6b5bb3': 'Sw65',
+
         'io6b5b3': 'Ger65',
+        # Ger7 = iø7 -- is wrong...
+        'iob64b3': 'Ger43',
+        'iob6b42': 'Ger42',
     }
     minorSeventhSubs = {
         'b75b3': '7',
@@ -950,13 +1062,18 @@ def romanNumeralFromChord(
               and chordObj.isSeventhOfType((0, 3, 7, 11))):
         rnString = ft.prefix + stepRoman + minorMajorSeventhSubs[inversionString]
 
-    elif not noKeyGiven and rnString in aug6subs and chordObj.isAugmentedSixth():
+    elif (not noKeyGiven
+          and rnString in aug6subs
+          and chordObj.isAugmentedSixth(permitAnyInversion=True)):
         rnString = aug6subs[rnString]
-    elif noKeyGiven and rnString in aug6NoKeyObjectSubs and chordObj.isAugmentedSixth():
+    elif (noKeyGiven
+          and rnString in aug6NoKeyObjectSubs
+          and chordObj.isAugmentedSixth(permitAnyInversion=True)):
         rnString = aug6NoKeyObjectSubs[rnString]
-        if rnString in ('It6', 'Ger65'):
+        nationalityStart = rnString[:2]  # nb: Ger = Ge
+        if nationalityStart in ('It', 'Ge'):
             keyObj = _getKeyFromCache(chordObj.fifth.name.lower())
-        elif rnString in ('Fr43', 'Sw43'):
+        elif nationalityStart in ('Fr', 'Sw'):
             keyObj = _getKeyFromCache(chordObj.seventh.name.lower())
 
     try:
@@ -1298,7 +1415,7 @@ class RomanNumeral(harmony.Harmony):
     'half-diminished seventh chord'
 
     >>> alteredChordHalfDim3rdInv.romanNumeral
-    '-ii'
+    'bii'
 
     >>> alteredChordHalfDim3rdInv.romanNumeralAlone
     'ii'
@@ -1347,8 +1464,8 @@ class RomanNumeral(harmony.Harmony):
     Just for kicks (no worries if this is goobley-gook):
 
     >>> ots = scale.OctatonicScale('C2')
-    >>> romanNumeral = roman.RomanNumeral('I9', ots, caseMatters=False)
-    >>> cp(romanNumeral)
+    >>> rn_I9 = roman.RomanNumeral('I9', ots, caseMatters=False)
+    >>> cp(rn_I9)
     ['C2', 'E-2', 'G-2', 'A2', 'C3']
 
     >>> romanNumeral2 = roman.RomanNumeral(
@@ -1356,17 +1473,17 @@ class RomanNumeral(harmony.Harmony):
     >>> cp(romanNumeral2)
     ['G-2', 'A-2', 'C#3', 'E-3']
 
-    >>> romanNumeral = roman.RomanNumeral('v64/V', key.Key('e'))
-    >>> romanNumeral
+    >>> rn_minor_64_secondary = roman.RomanNumeral('v64/V', key.Key('e'))
+    >>> rn_minor_64_secondary
     <music21.roman.RomanNumeral v64/V in e minor>
 
-    >>> romanNumeral.figure
+    >>> rn_minor_64_secondary.figure
     'v64/V'
 
-    >>> cp(romanNumeral)
+    >>> cp(rn_minor_64_secondary)
     ['C#5', 'F#5', 'A5']
 
-    >>> romanNumeral.secondaryRomanNumeral
+    >>> rn_minor_64_secondary.secondaryRomanNumeral
     <music21.roman.RomanNumeral V in e minor>
 
     Dominant 7ths can be specified by putting d7 at end:
@@ -1488,6 +1605,41 @@ class RomanNumeral(harmony.Harmony):
     >>> r.pitches
     ()
 
+    Equality:
+
+    Two RomanNumerals compare equal if their `NotRest` components
+    (noteheads, beams, expressions, articulations, etc.) are equal
+    and if their figures and keys are equal:
+
+    >>> c1 = chord.Chord('C4 E4 G4 C5')
+    >>> c2 = chord.Chord('C3 E4 G4')
+    >>> rn1 = roman.romanNumeralFromChord(c1, 'C')
+    >>> rn2 = roman.romanNumeralFromChord(c2, 'C')
+    >>> rn1 == rn2
+    True
+    >>> rn1.duration.type = 'half'
+    >>> rn1 == rn2
+    False
+    >>> rn3 = roman.RomanNumeral('I', 'd')
+    >>> rn2 == rn3
+    False
+    >>> rn3.key = key.Key('C')
+    >>> rn2 == rn3
+    True
+    >>> rn4 = roman.RomanNumeral('ii', 'C')
+    >>> rn2 == rn4
+    False
+    >>> rn4.figure = 'I'
+    >>> rn2 == rn4
+    True
+
+    Changed in v6.5 -- caseMatters is keyword only. It along with sixthMinor and
+    seventhMinor are now the only allowable keywords to pass in.
+
+    Changed in v7 -- RomanNumeral.romanNumeral will always give a "b" for a flattened
+    degree (i.e., '-II' becomes 'bII') as this is what people expect in looking at
+    the figure.
+
 
     OMIT_FROM_DOCS
 
@@ -1532,37 +1684,7 @@ class RomanNumeral(harmony.Harmony):
     >>> cp(r)
     ['G4']
 
-
-    Equality:
-
-    Two RomanNumerals compare equal if their `NotRest` components
-    (noteheads, beams, expressions, articulations, etc.) are equal
-    and if their figures and keys are equal:
-
-    >>> c1 = chord.Chord('C4 E4 G4 C5')
-    >>> c2 = chord.Chord('C3 E4 G4')
-    >>> rn1 = roman.romanNumeralFromChord(c1, 'C')
-    >>> rn2 = roman.romanNumeralFromChord(c2, 'C')
-    >>> rn1 == rn2
-    True
-    >>> rn1.duration.type = 'half'
-    >>> rn1 == rn2
-    False
-    >>> rn3 = roman.RomanNumeral('I', 'd')
-    >>> rn2 == rn3
-    False
-    >>> rn3.key = key.Key('C')
-    >>> rn2 == rn3
-    True
-    >>> rn4 = roman.RomanNumeral('ii', 'C')
-    >>> rn2 == rn4
-    False
-    >>> rn4.figure = 'I'
-    >>> rn2 == rn4
-    True
-
-    Changed in v6.5 -- caseMatters is keyword only. It along with sixthMinor and
-    seventhMinor are now the only allowable keywords to pass in.
+    (NOTE: all this is omitted -- look at OMIT_FROM_DOCS above)
     '''
     # TODO: document better! what is inherited and what is new?
 
@@ -1570,9 +1692,11 @@ class RomanNumeral(harmony.Harmony):
     _omittedStepsRegex = re.compile(r'(\[(no[1-9]+)+]\s*)+')
     _addedStepsRegex = re.compile(r'\[add(b*|-*|#*)(\d+)+]\s*')
     _bracketedAlterationRegex = re.compile(r'\[(b+|-+|#+)(\d+)]')
-    _augmentedSixthRegex = re.compile(r'(It|Ger|Fr|Sw)')
+    _augmentedSixthRegex = re.compile(r'(It|Ger|Fr|Sw)\+?')
     _romanNumeralAloneRegex = re.compile(r'(IV|I{1,3}|VI{0,2}|iv|i{1,3}|vi{0,2}|N)')
     _secondarySlashRegex = re.compile(r'(.*?)/([#a-np-zA-NP-Z].*)')
+    _aug6defaultInversions = {'It': '6', 'Fr': '43', 'Ger': '65', 'Sw': '43'}
+    _slashedAug6Inv = re.compile(r'(\d)/(\d)')
 
     _DOC_ATTR = {
         'addedSteps': '''
@@ -2485,62 +2609,85 @@ class RomanNumeral(harmony.Harmony):
         >>> rn.romanNumeralAlone
         'V'
 
+        >>> rn = roman.RomanNumeral()
+        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('Ger65', useScale)
+        >>> workingFig
+        '65'
+        >>> rn.scaleDegreeWithAlteration
+        (4, <music21.pitch.Accidental sharp>)
+
+
+        Working figures might be changed to defaults:
+
         >>> rn = roman.RomanNumeral()
         >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('Fr+6', useScale)
         >>> workingFig
-        '+6'
+        '43'
         >>> outScale
         <music21.key.Key of c minor>
         >>> rn.scaleDegree
         2
 
-        >>> rn = roman.RomanNumeral()
-        >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('Ger65', useScale)
-        >>> rn.scaleDegreeWithAlteration
-        (4, <music21.pitch.Accidental sharp>)
-
         >>> rn = roman.RomanNumeral()
         >>> workingFig, outScale = rn._parseRNAloneAmidstAug6('It6', scale.MajorScale('C'))
         >>> outScale
         <music21.key.Key of c minor>
         '''
-        if (not self._romanNumeralAloneRegex.match(workingFigure)
-                and not self._augmentedSixthRegex.match(workingFigure)):
+        romanNormalMatch = self._romanNumeralAloneRegex.match(workingFigure)
+        aug6Match = self._augmentedSixthRegex.match(workingFigure)  # 250ns not worth short-circuit
+
+        if not romanNormalMatch and not aug6Match:
             raise RomanException(f'No roman numeral found in {workingFigure!r}')  # pragma: no cover
 
-        if self._augmentedSixthRegex.match(workingFigure):
+        if aug6Match:
             # NB -- could be Key or Scale
             if (('Key' in useScale.classes and useScale.mode == 'major')
                     or ('DiatonicScale' in useScale.classes and useScale.type == 'major')):
                 useScale = key.Key(useScale.tonic, 'minor')
                 self.impliedScale = useScale
                 self.useImpliedScale = True
-                # Set secondary key to minor, if any
+
+                # Set secondary key, if any, to minor
                 if self.secondaryRomanNumeralKey is not None:
-                    self.secondaryRomanNumeralKey = key.Key(
-                        self.secondaryRomanNumeralKey.tonic, 'minor')
-            rm = self._augmentedSixthRegex.match(workingFigure)
-            romanNumeralAlone = rm.group(1)
-            if romanNumeralAlone in ('It', 'Ger'):
+                    secondary_tonic = self.secondaryRomanNumeralKey.tonic
+                    self.secondaryRomanNumeralKey = key.Key(secondary_tonic, 'minor')
+
+            # when Python 3.7 support is removed
+            # aug6type: Literal['It', 'Ger', 'Fr', 'Sw'] = aug6Match.group(1)
+            aug6type = aug6Match.group(1)
+
+            if aug6type in ('It', 'Ger'):
                 self.scaleDegree = 4
                 self.frontAlterationAccidental = pitch.Accidental('sharp')
-            elif romanNumeralAlone in ('Fr',):
+            elif aug6type == 'Fr':
                 self.scaleDegree = 2
-            elif romanNumeralAlone in ('Sw',):
+            elif aug6type == 'Sw':
                 self.scaleDegree = 2
                 self.frontAlterationAccidental = pitch.Accidental('sharp')
 
             workingFigure = self._augmentedSixthRegex.sub('', workingFigure)
-            self.romanNumeralAlone = romanNumeralAlone
-            if romanNumeralAlone != 'Fr':
+            workingFigure = self._slashedAug6Inv.sub(r'\1\2', workingFigure)
+
+            if not workingFigure or not workingFigure[0].isdigit():
+                # Ger was passed in instead of Ger65, etc.
+                workingFigure = self._aug6defaultInversions[aug6type] + workingFigure
+            elif (workingFigure
+                  and aug6type != 'It'
+                  and workingFigure[0] == '6'
+                  and (len(workingFigure) < 2
+                        or not workingFigure[1].isdigit())):
+                # Fr6 => Fr43
+                workingFigure = self._aug6defaultInversions[aug6type] + workingFigure[1:]
+
+            self.romanNumeralAlone = aug6type
+            if aug6type != 'Fr':
                 fixTuple = ('#', 1)
                 self.bracketedAlterations.append(fixTuple)
-            if romanNumeralAlone in ('Fr', 'Sw'):
+            if aug6type in ('Fr', 'Sw'):
                 fixTuple = ('#', 3)
                 self.bracketedAlterations.append(fixTuple)
         else:
-            rm = self._romanNumeralAloneRegex.match(workingFigure)
-            romanNumeralAlone = rm.group(1)
+            romanNumeralAlone = romanNormalMatch.group(1)
             self.scaleDegree = common.fromRoman(romanNumeralAlone)
             workingFigure = self._romanNumeralAloneRegex.sub('', workingFigure)
             self.romanNumeralAlone = romanNumeralAlone
@@ -2781,19 +2928,32 @@ class RomanNumeral(harmony.Harmony):
     def romanNumeral(self):
         '''
         Read-only property that returns either the romanNumeralAlone (e.g. just
-        II) or the frontAlterationAccidental.modifier + romanNumeralAlone (e.g. #II)
+        II) or the frontAlterationAccidental.modifier (with 'b' for '-') + romanNumeralAlone
+        (e.g. #II, bII)
 
         >>> from music21 import roman
         >>> rn = roman.RomanNumeral('#II7')
         >>> rn.romanNumeral
         '#II'
 
+        >>> rn = roman.RomanNumeral('Ger+6')
+        >>> rn.romanNumeral
+        'Ger'
+
+        >>> rn = roman.RomanNumeral('bbII/V')
+        >>> rn.romanNumeral
+        'bbII'
+        >>> rn = roman.RomanNumeral('--II/V')
+        >>> rn.romanNumeral
+        'bbII'
         '''
+        if self.romanNumeralAlone in ('Ger', 'Sw', 'It', 'Fr'):
+            return self.romanNumeralAlone
         if self.frontAlterationAccidental is None:
             return self.romanNumeralAlone
-        else:
-            return (self.frontAlterationAccidental.modifier
-                    + self.romanNumeralAlone)
+
+        return (self.frontAlterationAccidental.modifier.replace('-', 'b')
+                + self.romanNumeralAlone)
 
     @property
     def figure(self):
@@ -3696,21 +3856,64 @@ class Test(unittest.TestCase):
         def p(c):
             return ' '.join([x.nameWithOctave for x in c.pitches])
 
-        for kStr in ('a', 'A'):
-            k = key.Key(kStr)
-
-            rn = roman.RomanNumeral('It6', k)
-            self.assertEqual(p(rn), 'F5 A5 D#6')
-            rn = roman.RomanNumeral('Ger65', k)
-            self.assertEqual(p(rn), 'F5 A5 C6 D#6')
-            rn = roman.RomanNumeral('Ger6/5', k)
-            self.assertEqual(p(rn), 'F5 A5 C6 D#6')
-            rn = roman.RomanNumeral('Fr43', k)
-            self.assertEqual(p(rn), 'F5 A5 B5 D#6')
-            rn = roman.RomanNumeral('Fr4/3', k)
-            self.assertEqual(p(rn), 'F5 A5 B5 D#6')
-            rn = roman.RomanNumeral('Sw43', k)
-            self.assertEqual(p(rn), 'F5 A5 B#5 D#6')
+        def test_numeral(country, figure_list, result, key_in='a'):
+            for figure in figure_list:
+                for with_plus in ('', '+'):
+                    for kStr in (key_in, key_in.upper()):
+                        key_obj = key.Key(kStr)
+                        rn_str = country + with_plus + figure
+                        rn = roman.RomanNumeral(rn_str, key_obj)
+                        self.assertEqual(p(rn), result)
+
+
+        test_numeral('It', ['6', ''], 'F5 A5 D#6')
+        test_numeral('Ger', ['', '6', '65', '6/5'], 'F5 A5 C6 D#6')
+        test_numeral('Fr', ['', '6', '43', '4/3'], 'F5 A5 B5 D#6')
+        test_numeral('Sw', ['', '6', '43', '4/3'], 'F5 A5 B#5 D#6')
+
+        # these I worked out in C, not A ...  :-)
+        test_numeral('It', ['53'], 'F#4 A-4 C5', 'C')
+        test_numeral('It', ['64'], 'C4 F#4 A-4', 'C')
+        test_numeral('Ger', ['7'], 'F#4 A-4 C5 E-5', 'C')
+        test_numeral('Ger', ['43'], 'C4 E-4 F#4 A-4', 'C')
+        test_numeral('Ger', ['42'], 'E-4 F#4 A-4 C5', 'C')
+        test_numeral('Fr', ['7'], 'D4 F#4 A-4 C5', 'C')
+        test_numeral('Fr', ['65'], 'F#4 A-4 C5 D5', 'C')
+        test_numeral('Fr', ['42'], 'C4 D4 F#4 A-4', 'C')
+        test_numeral('Sw', ['7'], 'D#4 F#4 A-4 C5', 'C')
+        test_numeral('Sw', ['65'], 'F#4 A-4 C5 D#5', 'C')
+        test_numeral('Sw', ['42'], 'C4 D#4 F#4 A-4', 'C')
+
+    def test_augmented_round_trip(self):
+        # only testing properly spelled forms for input, since output will
+        # always be properly spelled
+        augTests = [
+            'It6', 'It64', 'It53',
+            'Ger65', 'Ger43', 'Ger42', 'Ger7',
+            'Fr43', 'Fr7', 'Fr42', 'Fr65',
+            'Sw43', 'Sw7', 'Sw42', 'Sw65',
+        ]
+
+        c_minor = key.Key('c')
+        c_major = key.Key('C')
+
+        for aug6 in augTests:
+            rn = RomanNumeral(aug6, c_minor)
+            ch = chord.Chord(rn.pitches)
+            # without key...
+            rn_out = romanNumeralFromChord(ch)
+            if aug6 not in ('Ger7', 'Fr7'):
+                # TODO(msc): fix these -- currently giving iø7 and Iø7 respectively
+                self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
+                self.assertEqual(rn_out.key.tonicPitchNameWithCase, 'c')
+
+            # with key
+            rn_out = romanNumeralFromChord(ch, c_minor)
+            self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
+
+            rn_out = romanNumeralFromChord(ch, c_major)
+            self.assertEqual(rn.figure, rn_out.figure, f'{aug6}: {rn_out}')
+
 
     def testZeroForDiminished(self):
         from music21 import roman
diff --git a/music21/romanText/tsvConverter.py b/music21/romanText/tsvConverter.py
index d0cf1dcd9..3c731d77e 100644
--- a/music21/romanText/tsvConverter.py
+++ b/music21/romanText/tsvConverter.py
@@ -720,6 +720,7 @@ class Test(unittest.TestCase):
         self.assertEqual(out_stream.parts[0].measure(1)[0].figure, 'I')  # First item in measure 1
 
     def testM21ToTsv(self):
+        import os
         from music21 import corpus
 
         bachHarmony = corpus.parse('bach/choraleAnalyses/riemenschneider001.rntxt')
@@ -734,6 +735,7 @@ class Test(unittest.TestCase):
         initial.write(tempF)
         handler = TsvHandler(tempF)
         self.assertEqual(handler.tsvData[0][0], 'I')
+        os.remove(tempF)
 
     def testIsMinor(self):
         self.assertTrue(is_minor('f'))
diff --git a/music21/scale/intervalNetwork.py b/music21/scale/intervalNetwork.py
index 9d52dbdfb..8d3778ee5 100644
--- a/music21/scale/intervalNetwork.py
+++ b/music21/scale/intervalNetwork.py
@@ -33,12 +33,6 @@ import copy
 import unittest
 
 from collections import OrderedDict
-try:
-    import networkx
-except ImportError:
-    # lacking this does nothing
-    networkx = None
-    # _missingImport.append('networkx')
 
 from music21 import exceptions21
 from music21 import interval
@@ -2189,6 +2183,8 @@ class IntervalNetwork:
         of this IntervalNetwork if networkx is installed
 
         '''
+        # noinspection PyPackageRequirements
+        import networkx  # pylint: disable=import-error
         weight = 1
         style = 'solid'
 
@@ -2244,14 +2240,14 @@ class IntervalNetwork:
 
         Requires networkx to be installed.
         '''
-#
-#         >>> s = corpus.parse('bach/bwv324.xml') #_DOCS_HIDE
-#         >>> s.plot('pianoroll', doneAction=None) #_DOCS_HIDE
-#         >>> #_DOCS_SHOW s = corpus.parse('bach/bwv57.8')
-#         >>> #_DOCS_SHOW s.plot('pianoroll')
-
-#         .. image:: images/PlotHorizontalBarPitchSpaceOffset.*
-#             :width: 600
+        #
+        # >>> s = corpus.parse('bach/bwv324.xml') #_DOCS_HIDE
+        # >>> s.plot('pianoroll', doneAction=None) #_DOCS_HIDE
+        # >>> #_DOCS_SHOW s = corpus.parse('bach/bwv57.8')
+        # >>> #_DOCS_SHOW s.plot('pianoroll')
+        #
+        # .. image:: images/PlotHorizontalBarPitchSpaceOffset.*
+        #     :width: 600
         if pitchObj is None:
             pitchObj = pitch.Pitch('C4')
 
diff --git a/music21/stream/base.py b/music21/stream/base.py
index 41c478fcd..1ea8d8a05 100644
--- a/music21/stream/base.py
+++ b/music21/stream/base.py
@@ -24,6 +24,7 @@ import collections
 import copy
 import itertools
 import math
+import os
 import pathlib
 import unittest
 import sys
@@ -1165,7 +1166,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         in the other Stream in this Stream. This does not make
         copies of any elements, but simply stores all of them in this Stream.
 
-        Optionally, provide a list of classes to exclude with the `classFilter` list.
+        Optionally, provide a list of classes to include with the `classFilter` list.
 
         This method provides functionality like a shallow copy,
         but manages locations properly, only copies elements,
@@ -1185,6 +1186,16 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         True
         >>> s1[1] is s2[1]
         True
+
+        >>> viola = instrument.Viola()
+        >>> trumpet = instrument.Trumpet()
+        >>> s1.insert(0, viola)
+        >>> s1.insert(0, trumpet)
+        >>> s2.mergeElements(s1, classFilterList=('BrassInstrument',))
+        >>> len(s2)
+        3
+        >>> viola in s2
+        False
         '''
         if classFilterList is not None:
             classFilterSet = set(classFilterList)
@@ -2588,7 +2599,8 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         Given a `target` object, replace it with
         the supplied `replacement` object.
 
-        Does nothing if target cannot be found.
+        Does nothing if target cannot be found. Raises StreamException if replacement
+        is already in the stream.
 
         If `allDerived` is True (as it is by default), all sites (stream) that
         this this stream derives from and also
@@ -2657,6 +2669,8 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         in stream twice.  recurse and shiftOffsets changed to keywordOnly arguments
 
         Changed in v6 -- recurse works
+
+        Changed in v7 -- raises StreamException if replacement is already in the stream.
         '''
         def replaceDerived(startSite=self):
             if not allDerived:
@@ -2669,6 +2683,14 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                                         recurse=recurse,
                                         allDerived=False)
 
+        try:
+            i = self.index(replacement)
+        except StreamException:
+            # good. now continue.
+            pass
+        else:
+            raise StreamException(f'{replacement} already in {self}')
+
         try:
             i = self.index(target)
         except StreamException:
@@ -3079,8 +3101,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
 
     # --------------------------------------------------------------------------
     # methods that act on individual elements without requiring
-    # @ coreElementsChanged to fire
-
+    # coreElementsChanged to fire
     def addGroupForElements(self, group, classFilter=None, *, recurse=False):
         '''
         Add the group to the groups attribute of all elements.
@@ -4439,6 +4460,15 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 else:
                     out.coreStoreAtEnd(elNew)
 
+        # Replace old measures in spanners with new measures
+        # Example: out is a Part, out.spannerBundle has RepeatBrackets spanning measures
+        # TODO: when dropping support for Py3.9 add strict=True
+        for oldM, newM in zip(
+            self.getElementsByClass('Measure'),
+            out.getElementsByClass('Measure')
+        ):
+            out.spannerBundle.replaceSpannedElement(oldM, newM)
+
         optionalAddRest()
         out.coreElementsChanged()
 
@@ -6286,17 +6316,20 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                  meterStream=None,
                  inPlace=False,
                  displayTiedAccidentals=False,
+                 classFilterList=(note.GeneralNote,),
                  ):
         '''
         Calls :py:func:`~music21.stream.makeNotation.makeTies`.
 
         Changed in v.4., inPlace=False by default.
+        Added in v.7, `classFilterList`.
         '''
         return makeNotation.makeTies(
             self,
             meterStream=meterStream,
             inPlace=inPlace,
             displayTiedAccidentals=displayTiedAccidentals,
+            classFilterList=classFilterList,
         )
 
     def makeBeams(self, *, inPlace=False, setStemDirections=True):
@@ -7872,8 +7905,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
             return self._cache['Duration']
         else:
             # environLocal.printDebug(['creating new duration based on highest time'])
-            self._cache['Duration'] = duration.Duration()
-            self._cache['Duration'].quarterLength = self.highestTime
+            self._cache['Duration'] = duration.Duration(quarterLength=self.highestTime)
             return self._cache['Duration']
 
     def _setDuration(self, durationObj):
@@ -10601,6 +10633,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> ce = c.voicesToParts()
         >>> ce.show('t')
         {0.0} <music21.stream.Part Piano-v0>
+            {0.0} <music21.instrument.Instrument 'P1: Piano: '>
             {0.0} <music21.stream.Measure 1 offset=0.0>
                 {0.0} <music21.clef.TrebleClef>
                 {0.0} <music21.key.Key of D major>
@@ -10616,6 +10649,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.note.Rest rest>
                 {4.0} <music21.bar.Barline type=final>
         {0.0} <music21.stream.Part Piano-v1>
+            {0.0} <music21.instrument.Instrument 'P1: Piano: '>
             {0.0} <music21.stream.Measure 1 offset=0.0>
                 {0.0} <music21.clef.BassClef>
                 {0.0} <music21.key.Key of D major>
@@ -10750,6 +10784,10 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                     pInner = partDict[voiceIdInner]
                     pInner.insert(self.elementOffset(mInner), copy.deepcopy(mActive))
 
+        # Place references to any instruments from the original part into the new parts
+        for p in s.parts:
+            p.mergeElements(self, classFilterList=('Instrument',))
+
         if self.hasMeasures():
             for m in self.iter.getElementsByClass('Measure'):
                 if m.hasVoices():
@@ -13799,12 +13837,15 @@ class Opus(Stream):
         elif common.runningUnderIPython():
             return Stream.write(self, fmt, fp, **keywords)
 
+        delete = False
         if fp is None:
             if fmt is None:
                 suffix = '.' + environLocal['writeFormat']
             else:
                 unused_format, suffix = common.findFormat(fmt)
-            fp = environLocal.getTempFile(returnPathlib=False) + suffix
+            fp = environLocal.getTempFile(suffix=suffix, returnPathlib=False)
+            # Mark for deletion, because it won't actually be used
+            delete = True
         if isinstance(fp, str):
             fp = pathlib.Path(fp)
 
@@ -13824,6 +13865,10 @@ class Opus(Stream):
             fpReturned = s.write(fmt=fmt, fp=fpToUse, **keywords)
             environLocal.printDebug(f'Component {s} written to {fpReturned}')
             post.append(fpReturned)
+
+        if delete:
+            os.remove(fp)
+
         return post[-1] if post else None
 
     def show(self, fmt=None, app=None, **keywords):
@@ -13888,6 +13933,25 @@ class SpannerStorage(Stream):
     def coreStoreAtEnd(self, element, setActiveSite=True):  # pragma: no cover
         raise StreamException('SpannerStorage cannot store at end.')
 
+    def replace(self,
+                target: base.Music21Object,
+                replacement: base.Music21Object,
+                *,
+                recurse: bool = False,
+                allDerived: bool = True) -> None:
+        '''
+        Overrides :meth:`~music21.stream.Stream.replace` in order to check first
+        whether `replacement` already exists in `self`. If so, delete `target` from
+        `self` and return; otherwise call the superclass method.
+
+        New in v7.
+        '''
+        # Does not perform a recursive search, but shouldn't need to
+        if replacement in self:
+            self.remove(target)
+            return
+        super().replace(target, replacement, recurse=recurse, allDerived=allDerived)
+
 
 class VariantStorage(Stream):
     '''
diff --git a/music21/stream/iterator.py b/music21/stream/iterator.py
index 26cdbf877..9df634b46 100644
--- a/music21/stream/iterator.py
+++ b/music21/stream/iterator.py
@@ -20,6 +20,7 @@ import unittest
 import warnings
 
 from music21 import common
+from music21.common.classTools import tempAttribute, saveAttributes
 from music21.common.enums import OffsetSpecial
 from music21.exceptions21 import StreamException
 from music21.stream import filters
@@ -111,7 +112,7 @@ class StreamIterator(prebase.ProtoM21Object):
         self.iterSection = '_elements'
 
         self.cleanupOnStop = False
-        self.restoreActiveSites = restoreActiveSites
+        self.restoreActiveSites: bool = restoreActiveSites
 
         self.overrideDerivation = None
 
@@ -365,7 +366,7 @@ class StreamIterator(prebase.ProtoM21Object):
         '''
         if self._len is not None:
             return self._len
-        self._len = len(self.matchingElements())
+        self._len = len(self.matchingElements(restoreActiveSites=False))
         self.reset()
         return self._len
 
@@ -415,8 +416,12 @@ class StreamIterator(prebase.ProtoM21Object):
         '''
         if self._len is not None:
             return bool(self._len)
-        for unused in self:
-            return True
+
+        # do not change active site of first element in bool
+        with tempAttribute(self, 'restoreActiveSites', False):
+            for unused in self:
+                return True
+
         return False
 
     def clone(self: _SIter) -> _SIter:
@@ -572,7 +577,7 @@ class StreamIterator(prebase.ProtoM21Object):
     # ---------------------------------------------------------------
     # getting items
 
-    def matchingElements(self):
+    def matchingElements(self, *, restoreActiveSites: bool = True):
         '''
         returns a list of elements that match the filter.
 
@@ -601,7 +606,8 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> sI_notes
         <music21.stream.iterator.StreamIterator for Part:tn3/4 @:0>
 
-        Note that this used to be True until v6.0.3
+        Adding a filter to the Stream iterator returns a new Stream iterator; it
+        does not change the original.
 
         >>> sI_notes is sI
         False
@@ -616,21 +622,24 @@ class StreamIterator(prebase.ProtoM21Object):
         [<music21.note.Note C>, <music21.note.Note D>,
          <music21.note.Note E>, <music21.note.Note F>, <music21.note.Note G>,
          <music21.note.Note A>]
+
+        If restoreActiveSties is False then the elements will not have
+        their activeSites changed (callers should use it when they do not plan to actually
+        expose the elements to users, such as in `__len__`).
+
+        Added in v7. -- restoreActiveSites
         '''
         if self._matchingElements is not None:
             return self._matchingElements
 
-        savedIndex = self.index
-        savedRestoreActiveSites = self.restoreActiveSites
-        self.restoreActiveSites = True
-
-        me = [x for x in self]  # pylint: disable=unnecessary-comprehension
-
-        self.reset()
+        with saveAttributes(self, 'restoreActiveSites', 'index'):
+            self.restoreActiveSites = restoreActiveSites
+            me = [x for x in self]  # pylint: disable=unnecessary-comprehension
+            self.reset()
 
-        self.index = savedIndex
-        self.restoreActiveSites = savedRestoreActiveSites
-        self._matchingElements = me
+        if restoreActiveSites == self.restoreActiveSites:
+            # cache, if we are using the iterator default.
+            self._matchingElements = me
 
         return me
 
@@ -1604,12 +1613,11 @@ class RecursiveIterator(StreamIterator):
         self.activeInformation['lastYielded'] = None
         super().reset()
 
-    def matchingElements(self):
+    def matchingElements(self, *, restoreActiveSites=True):
         # saved parent iterator later?
         # will this work in mid-iteration? Test, or do not expose till then.
-        savedRecursiveIterator = self.childRecursiveIterator
-        fe = super().matchingElements()
-        self.childRecursiveIterator = savedRecursiveIterator
+        with tempAttribute(self, 'childRecursiveIterator'):
+            fe = super().matchingElements(restoreActiveSites=restoreActiveSites)
         return fe
 
     def iteratorStack(self):
diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py
index 938d63ef6..54d1655fc 100644
--- a/music21/stream/makeNotation.py
+++ b/music21/stream/makeNotation.py
@@ -34,7 +34,7 @@ environLocal = environment.Environment(__file__)
 
 
 def makeBeams(
-    s,
+    s: 'music21.stream.Stream',
     *,
     inPlace=False,
     setStemDirections=True,
@@ -97,12 +97,14 @@ def makeBeams(
 
     # environLocal.printDebug(['calling Stream.makeBeams()'])
     if not inPlace:  # make a copy
-        returnObj = copy.deepcopy(s)
+        returnObj: stream.Stream = copy.deepcopy(s)
     else:
-        returnObj = s
+        returnObj: stream.Stream = s
 
     # if s.isClass(Measure):
+    mColl: List[stream.Measure]
     if 'Measure' in s.classes:
+        returnObj: stream.Measure
         mColl = [returnObj]  # store a list of measures for processing
     else:
         mColl = list(returnObj.iter.getElementsByClass('Measure'))  # a list of measures
@@ -112,6 +114,7 @@ def makeBeams(
 
     lastTimeSignature = None
 
+    m: stream.Measure
     for m in mColl:
         # this means that the first of a stream of time signatures will
         # be used
@@ -764,27 +767,28 @@ def makeRests(
     4.0
     >>> a.insert(0, meter.TimeSignature('4/4'))
     >>> a.makeMeasures(inPlace=True)
-    >>> a.show('text')
-    {0.0} <music21.stream.Measure 1 offset=0.0>
-        {0.0} <music21.clef.TrebleClef>
-        {0.0} <music21.meter.TimeSignature 4/4>
-    {4.0} <music21.stream.Measure 2 offset=4.0>
-        {0.0} <music21.note.Note C>
-    {8.0} <music21.stream.Measure 3 offset=8.0>
-        {0.0} <music21.note.Note D>
-        {1.0} <music21.bar.Barline type=final>
+    >>> a.show('text', addEndTimes=True)
+    {0.0 - 0.0} <music21.stream.Measure 1 offset=0.0>
+        {0.0 - 0.0} <music21.clef.TrebleClef>
+        {0.0 - 0.0} <music21.meter.TimeSignature 4/4>
+    {4.0 - 5.0} <music21.stream.Measure 2 offset=4.0>
+        {0.0 - 1.0} <music21.note.Note C>
+    {8.0 - 9.0} <music21.stream.Measure 3 offset=8.0>
+        {0.0 - 1.0} <music21.note.Note D>
+        {1.0 - 1.0} <music21.bar.Barline type=final>
     >>> a.makeRests(fillGaps=True, inPlace=True)
-    >>> a.show('text')
-    {0.0} <music21.stream.Measure 1 offset=0.0>
-        {0.0} <music21.clef.TrebleClef>
-        {0.0} <music21.meter.TimeSignature 4/4>
-    {0.0} <music21.note.Rest rest>
-    {4.0} <music21.stream.Measure 2 offset=4.0>
-        {0.0} <music21.note.Note C>
-    {5.0} <music21.note.Rest rest>
-    {8.0} <music21.stream.Measure 3 offset=8.0>
-        {0.0} <music21.note.Note D>
-        {1.0} <music21.bar.Barline type=final>
+    >>> a.show('text', addEndTimes=True)
+    {0.0 - 4.0} <music21.stream.Measure 1 offset=0.0>
+        {0.0 - 0.0} <music21.clef.TrebleClef>
+        {0.0 - 0.0} <music21.meter.TimeSignature 4/4>
+        {0.0 - 4.0} <music21.note.Rest rest>
+    {4.0 - 8.0} <music21.stream.Measure 2 offset=4.0>
+        {0.0 - 1.0} <music21.note.Note C>
+        {1.0 - 4.0} <music21.note.Rest rest>
+    {8.0 - 12.0} <music21.stream.Measure 3 offset=8.0>
+        {0.0 - 1.0} <music21.note.Note D>
+        {1.0 - 4.0} <music21.note.Rest rest>
+        {4.0 - 4.0} <music21.bar.Barline type=final>
 
     Changed in v6 -- all but first attribute are keyword only
 
@@ -808,6 +812,10 @@ def makeRests(
         if timeRangeFromBarDuration and returnObj.isMeasure:
             # NOTE: this will raise an exception if no meter can be found
             oHighTarget = returnObj.barDuration.quarterLength
+        elif timeRangeFromBarDuration and returnObj.hasMeasures():
+            oHighTarget = sum(
+                m.barDuration.quarterLength for m in returnObj.getElementsByClass(stream.Measure)
+            )
         else:
             oHighTarget = returnObj.highestTime
     elif isinstance(refStreamOrTimeRange, stream.Stream):
@@ -825,12 +833,18 @@ def makeRests(
         #    len(refStreamOrTimeRange)])
     if returnObj.hasVoices():
         bundle = list(returnObj.voices)
+    elif returnObj.hasMeasures():
+        bundle = returnObj.getElementsByClass('Measure')
     else:
         bundle = [returnObj]
 
+    # bundle components may be voices, measures, or a flat Stream
     for v in bundle:
         oLow = v.lowestOffset
         oHigh = v.highestTime
+        if returnObj.hasMeasures():
+            # adjust oHigh to not exceed measure
+            oHighTarget = min(v.barDuration.quarterLength, oHighTarget)
 
         # create rest from start to end
         qLen = oLow - oLowTarget
@@ -852,7 +866,6 @@ def makeRests(
             # place at oHigh to reach to oHighTarget
             v.insert(oHigh, r)
 
-
         if fillGaps:
             gapStream = v.findGaps()
             if gapStream is not None:
@@ -863,6 +876,24 @@ def makeRests(
                     v.insert(e.offset, r)
         # environLocal.printDebug(['post makeRests show()', v])
 
+    if returnObj.hasMeasures():
+        # split rests at measure boundaries
+        returnObj.makeTies(classFilterList=(note.Rest,))
+
+        # reposition measures
+        accumulatedTime = 0.0
+        for m in returnObj.getElementsByClass(stream.Measure):
+            returnObj.setElementOffset(m, accumulatedTime)
+            accumulatedTime += m.highestTime
+
+            # process voices
+            for v in m.voices:
+                v.makeRests(inPlace=True,
+                            fillGaps=fillGaps,
+                            hideRests=hideRests,
+                            refStreamOrTimeRange=m,
+                            )
+
     if inPlace is not True:
         return returnObj
 
@@ -872,12 +903,13 @@ def makeTies(
     *,
     meterStream=None,
     inPlace=False,
-    displayTiedAccidentals=False
+    displayTiedAccidentals=False,
+    classFilterList=(note.GeneralNote,),
 ):
     # noinspection PyShadowingNames
     '''
     Given a stream containing measures, examine each element in the
-    Stream. If the elements duration extends beyond the measure's boundary,
+    Stream. If the element's duration extends beyond the measure's boundary,
     create a tied entity, placing the split Note in the next Measure.
 
     Note that this method assumes that there is appropriate space in the
@@ -966,6 +998,40 @@ def makeTies(
     Notes: uses base.Music21Object.splitAtQuarterLength() once it has figured out
     what to split.
 
+    Changed in v. 4 -- inPlace = False by default.
+
+    Changed in v6 -- all but first attribute are keyword only
+
+    Added in v. 7 -- `classFilterList` acts as a filter on what elements will
+    be operated on (i.e. have durations split and/or ties made.)
+    The default `(note.GeneralNote,)` includes Notes, Chords, and Rests.
+
+    Here will we split and make ties only on Notes, leaving the too-long
+    rest in measure 1 alone.
+
+    >>> p = stream.Part()
+    >>> p.append(meter.TimeSignature('2/4'))
+    >>> p.insert(0.0, note.Rest(quarterLength=3.0))
+    >>> p.insert(3.0, note.Note(quarterLength=3.0))
+    >>> p.makeMeasures(inPlace=True)
+    >>> p.makeTies(classFilterList=[note.Note], inPlace=True)
+    >>> p.show('text', addEndTimes=True)
+    {0.0 - 3.0} <music21.stream.Measure 1 offset=0.0>
+        {0.0 - 0.0} <music21.clef.TrebleClef>
+        {0.0 - 0.0} <music21.meter.TimeSignature 2/4>
+        {0.0 - 3.0} <music21.note.Rest rest>
+    {2.0 - 4.0} <music21.stream.Measure 2 offset=2.0>
+        {1.0 - 2.0} <music21.note.Note C>
+    {4.0 - 6.0} <music21.stream.Measure 3 offset=4.0>
+        {0.0 - 2.0} <music21.note.Note C>
+        {2.0 - 2.0} <music21.bar.Barline type=final>
+    >>> p.measure(3).notes[0].tie
+    <music21.tie.Tie stop>
+
+    OMIT_FROM_DOCS
+
+    configure ".previous" and ".next" attributes
+
     Previously a note tied from one voice could not make ties into a note
     in the next measure outside of voices.  Fixed May 2017
 
@@ -1022,15 +1088,16 @@ def makeTies(
     <music21.note.Note B> None
     <music21.note.Note C> <music21.tie.Tie stop>
 
+    Be helpful and wrap `classFilterList` in a list if need be.
 
-    Changed in v4 -- inPlace = False by default.
-
-    Changed in v6 -- all but first attribute are keyword only
-
-    OMIT_FROM_DOCS
-    TODO: take a list of classes to act as filter on what elements are tied.
-
-    configure ".previous" and ".next" attributes
+    >>> m = stream.Measure([note.Note(quarterLength=8.0)])
+    >>> m.insert(0, meter.TimeSignature('4/4'))
+    >>> p = stream.Part([m])
+    >>> p.makeTies(inPlace=True, classFilterList='Note')
+    >>> len(p.getElementsByClass('Measure'))
+    2
+    >>> p.recurse().last().tie
+    <music21.tie.Tie stop>
     '''
     from music21 import stream
 
@@ -1044,6 +1111,9 @@ def makeTies(
     if not returnObj:
         raise stream.StreamException('cannot process an empty stream')
 
+    if not common.isIterable(classFilterList):
+        classFilterList = [classFilterList]
+
     # get measures from this stream
     measureStream = returnObj.getElementsByClass('Measure')
     if not measureStream:
@@ -1131,6 +1201,8 @@ def makeTies(
         # bundle components may be voices, or just a measure
         for v in bundle:
             for e in v:
+                if e.classSet.isdisjoint(classFilterList):
+                    continue
                 vId = v.id
                 # environLocal.printDebug([
                 #    'Stream.makeTies() iterating over elements in measure',
diff --git a/setup.py b/setup.py
index 185507f19..3c92cb1dc 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ with open(path, 'r') as f:
     lines = f.read()
     exec(lines)
 
-m21version = __version__ # @UndefinedVariable
+m21version = __version__
 
 DESCRIPTION = 'A Toolkit for Computer-Aided Musical Analysis.'
 DESCRIPTION_LONG = """A Toolkit for Computer-Aided Musical Analysis.


Test patch:
diff --git a/music21/metadata/testMetadata.py b/music21/metadata/testMetadata.py
index e8df5cb43..f7dcb4617 100644
--- a/music21/metadata/testMetadata.py
+++ b/music21/metadata/testMetadata.py
@@ -12,7 +12,7 @@ class Test(unittest.TestCase):
         from music21 import converter
         from music21.musicxml import testFiles as mTF
 
-        c = converter.parse(mTF.mozartTrioK581Excerpt)  # @UndefinedVariable
+        c = converter.parse(mTF.mozartTrioK581Excerpt)
         md = c.metadata
 
         self.assertEqual(md.movementNumber, '3')
@@ -23,7 +23,7 @@ class Test(unittest.TestCase):
         # get contributors directly from Metadata interface
         self.assertEqual(md.composer, 'Wolfgang Amadeus Mozart')
 
-        c = converter.parse(mTF.binchoisMagnificat)  # @UndefinedVariable
+        c = converter.parse(mTF.binchoisMagnificat)
         md = c.metadata
         self.assertEqual(md.composer, 'Gilles Binchois')
 
@@ -44,7 +44,7 @@ class Test(unittest.TestCase):
         self.assertEqual(md.title, 'Concerto in F')
 
         # test getting meta data from an imported source
-        c = converter.parse(mTF.mozartTrioK581Excerpt)  # @UndefinedVariable
+        c = converter.parse(mTF.mozartTrioK581Excerpt)
         md = c.metadata
 
         self.assertEqual(md.movementNumber, '3')
diff --git a/music21/stream/tests.py b/music21/stream/tests.py
index 1765e9ca6..eb18e713d 100644
--- a/music21/stream/tests.py
+++ b/music21/stream/tests.py
@@ -545,6 +545,24 @@ class Test(unittest.TestCase):
         self.assertEqual(a.duration.quarterLength, 2.0)
         self.assertEqual(a.highestTime, 4)
 
+    def testStreamDurationRecalculated(self):
+        from fractions import Fraction
+
+        a = Stream()
+        n = note.Note(quarterLength=1.0)
+        a.append(n)
+        self.assertEqual(a.duration.quarterLength, 1.0)
+
+        tup = duration.Tuplet()
+        n.duration.tuplets = (tup,)
+        self.assertEqual(a.duration.quarterLength, Fraction(2, 3))
+
+        # Also (regression) test clearing the cache
+        # https://github.com/cuthbertLab/music21/issues/957
+        n.duration.tuplets = (tup, tup)
+        a.coreElementsChanged()
+        self.assertEqual(a.duration.quarterLength, Fraction(4, 9))
+
     def testMeasureStream(self):
         '''An approach to setting TimeSignature measures in offsets and durations
         '''
@@ -1181,6 +1199,9 @@ class Test(unittest.TestCase):
         stripped = s.stripTies(inPlace=False)
         sn1 = stripped.flat.notes[0]
 
+        self.assertEqual(len(stripped.spanners[0]), 1)
+        self.assertEqual(len(stripped.spanners[1]), 1)
+
         self.assertTrue(stripped.spanners[0].isFirst(sn1))
         self.assertTrue(stripped.spanners[0].isLast(sn1))
         self.assertTrue(stripped.spanners[1].isFirst(sn1))
@@ -1906,9 +1927,7 @@ class Test(unittest.TestCase):
 
         s.insert(0, m1)
         s.insert(4, m2)
-        # must connect Measures to Streams before filling gaps
-        m1.makeRests(inPlace=True, fillGaps=True, timeRangeFromBarDuration=True)
-        m2.makeRests(inPlace=True, fillGaps=True, timeRangeFromBarDuration=True)
+        s.makeRests(inPlace=True, fillGaps=True, timeRangeFromBarDuration=True)
         self.assertTrue(m2.isSorted)
         # m2.sort()
 
@@ -1943,6 +1962,32 @@ class Test(unittest.TestCase):
         # s.show('text')
         # s.show()
 
+    def testMakeRestsInMeasures(self):
+        p = Part()
+        m1 = Measure()
+        m1.timeSignature = meter.TimeSignature('4/4')
+        m1.insert(2, note.Note())
+        m2 = Measure()
+        m2.insert(1, note.Note())
+        p.append(m1)
+        p.append(m2)
+
+        self.assertEqual(m1.duration.quarterLength, 3.0)
+        self.assertEqual(m2.duration.quarterLength, 2.0)
+        self.assertEqual(p.duration.quarterLength, 5.0)
+
+        for m in (m1, m2):
+            m.makeRests(inPlace=True, timeRangeFromBarDuration=True)
+
+        self.assertEqual(m1.duration.quarterLength, 4.0)
+        self.assertEqual(m2.duration.quarterLength, 4.0)
+
+        # m2 was never repositioned in p
+        self.assertEqual(p.duration.quarterLength, 7.0)
+
+        p.makeRests(inPlace=True)
+        self.assertEqual(p.duration.quarterLength, 8.0)
+
     def testMakeMeasuresInPlace(self):
         sScr = Stream()
         sScr.insert(0, clef.TrebleClef())
@@ -2105,6 +2150,10 @@ class Test(unittest.TestCase):
         s.replace(n4, n1)
         self.assertEqual([s[0], s[1]], [n3, n1])
 
+        error_msg = f'{n3} already in {s}'
+        with self.assertRaises(StreamException, msg=error_msg):
+            s.replace(n4, n3)
+
     def testReplaceA1(self):
         sBach = corpus.parse('bach/bwv324.xml')
         partSoprano = sBach.parts.first()
@@ -6092,7 +6141,7 @@ class Test(unittest.TestCase):
         from music21.musicxml import testFiles
         from music21 import converter
         # testing a file a file with dynamics
-        a = converter.parse(testFiles.schumannOp48No1)  # @UndefinedVariable
+        a = converter.parse(testFiles.schumannOp48No1)
         unused_b = a.flat
         # b= a.flat.extendDuration(dynamics.Dynamic)
 
@@ -8076,20 +8125,42 @@ class Test(unittest.TestCase):
         self.assertEqual(beams[4], stop_beam)  # last should be stop
 
     def testOpusWrite(self):
+        import os
+
         o = Opus()
         s1 = Score()
         s2 = Score()
         o.append([s1, s2])
 
         out = o.write()
+        otherFile = str(out).replace('-2', '-1')
         self.assertIsNotNone(out)
+        os.remove(out)
+        os.remove(otherFile)
 
-        out = o.write(fp=environLocal.getTempFile())
+        tmp = environLocal.getTempFile()
+        out = o.write(fp=tmp)
+        otherFile = str(out).replace('-2', '-1')
         self.assertTrue(str(out).endswith('-2.xml'))
+        self.assertTrue(os.path.exists(otherFile))
+        os.remove(tmp)
+        os.remove(out)
+        os.remove(otherFile)
 
         out = o.write(fmt='midi')
+        otherFile = str(out).replace('-2', '-1')
         self.assertTrue(str(out).endswith('-2.mid'))
+        self.assertTrue(os.path.exists(otherFile))
+        os.remove(out)
+        os.remove(otherFile)
 
+    def testActiveSiteAfterBoolIteration(self):
+        n = note.Note()
+        s1 = Stream([n])
+        s2 = Stream([n])
+        self.assertIs(n.activeSite, s2)
+        self.assertTrue(s1.notes)
+        self.assertIs(n.activeSite, s2)
 
 # -----------------------------------------------------------------------------
 
diff --git a/music21/test/commonTest.py b/music21/test/commonTest.py
index a9242a5b3..82288d01e 100644
--- a/music21/test/commonTest.py
+++ b/music21/test/commonTest.py
@@ -271,7 +271,7 @@ class ModuleGather:
         for dirPath, unused_dirNames, filenames in os.walk(self.dirParent):
             self._visitFunc(None, dirPath, filenames)
 
-        if common.cpus() > 4:  # @UndefinedVariable
+        if common.cpus() > 4:
             self.modulePaths.sort(key=manyCoreSortFunc)
         else:
             self.modulePaths.sort()
diff --git a/music21/test/multiprocessTest.py b/music21/test/multiprocessTest.py
index 060e7f34a..8a54e800a 100644
--- a/music21/test/multiprocessTest.py
+++ b/music21/test/multiprocessTest.py
@@ -191,7 +191,7 @@ def mainPoolRunner(testGroup=('test',), restoreEnvironmentDefaults=False, leaveO
             timeouts = 0
             eventsProcessed += 1
             summaryOutput.append(newResult)
-        except multiprocessing.TimeoutError:  # @UndefinedVariable
+        except multiprocessing.TimeoutError:
             timeouts += 1
             if timeouts == 5 and eventsProcessed > 0:
                 print('Delay in processing, seconds: ', end='')