Repository: cuthbertLab/music21
Pull request #965 (2021-04-18 21:52:40)

Remove Ambitus.getPitchRanges()
`s.analyze('range')` is now a synonym for `s.analyze('span')`, which is a much faster algorithm. That means in the rare cases where there were no instances of a repeated note in a stream, the previous nonzero minPitch is now zero.

***

Prevents segfaults I was seeing when trying to cache metadata.

**More information**
This algorithm is O(n^2), so any extra values should be filtered out of `psFound` before running an expensive computation. We don't need more than two instances of any `ps`, because they'll just produce the same interval.

https://github.com/cuthbertLab/music21/blob/ebc47a1fbc9d65981bc52928593b4b564ae96cd5/music21/analysis/discrete.py#L1079-L1091


While debuggging I printed `psRange` being computed for one stream to a file, and it consumed > 500 MB before I interrupted the interpreter.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/38915931/badge)](https://coveralls.io/builds/38915931)

Coverage increased (+9.0e-05%) to 92.153% when pulling **f5e501f4417e0207b97d71639f8f1ff766af0e80 on jacobtylerwalls:ambitus-ranges** into **450859475497dd70510e5ebde6184536bdac15a8 on cuthbertLab:master**.

mscuthbert: Curious -- are we ever using the min range anywhere in the system?  That seems like the main culprit for speed and I can't imagine what it might be useful for.  (In any real situation, it's going to be m2 or M2 I imagine.  Oh, maybe it shows if there are quarter tones?  But there's a better way to figure that out too).

We should be using `srcStream.pitches` -- much faster also.   (And Note also has a .pitches as well)
jacobtylerwalls: Having two instances of the same pitch in a Stream is sufficient to get a 0.0 min. So--no, not very useful! (Quarter tones shouldn't affect our decision, since `ps` is a float. So the same two quarter tones would yield 0.0.)

This is the only place it's used, to get colors for intervals (I suppose an interval of 0.0 needs a color?)

https://github.com/cuthbertLab/music21/blob/ebc47a1fbc9d65981bc52928593b4b564ae96cd5/music21/analysis/discrete.py#L970-L977

Mozart K155:
2455 notes
len(psRange) would be 2455
len(psFound) would be 2455 ** 2 = 6,027,025

PR:
len(psRange) 93
inner loop iterations would would be 93 ** 2 = 8,649

If you want it faster, yes, we could drop the second instance of the `ps` and just use a set

len(psRange) 47
inner loops 47 ** 2 = 2,209

And then change `_generateColors()` to always assume a min of 0.

> We should be using srcStream.pitches -- much faster also. (And Note also has a .pitches as well)

Nice, I can address this after you let me know your preference on the other point.
mscuthbert: WOW.  Your work DOES do 99.9% of the savings.  But yes, I think it's a time to remove getPitchRanges and rename to getAmbitusPs or something like that and in _generateColors assume a min of 0.

I think it can be removed without any deprecation cycle.  It's pretty hidden.
jacobtylerwalls: I think we can just remove. If we don't care about the minimum value any more, same result can be had getting the ambitus from the other method (`getPitchSpan()`) and taking the difference of the two values. No reason to do a second search through the score. (Future self: someday distill a reproducer for these segfaults not involving external files to report to cPython.)

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 686422476..b248a201f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -34,5 +34,6 @@ myStream2.insert(note.Rest(0.5))
 <!-- If relevant: OS, suggested fix, attempted workarounds -->
 
 <!-- Note: only Mac and Windows are directly supported by music21 staff.  
-     Bug reports that are specific to other OSes w/o a PR will be closed.
+     Bug reports that are specific to other OSes w/o a PR will be closed
+     unless the reporter is willing to contribute a fix.
  -->
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..71a53f166
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,27 @@
+---
+name: Feature request
+about: Suggest a feature to be included in music21
+title: ''
+labels: 'enhancement'
+assignees: ''
+
+---
+**Motivation**
+<!-- E.g. does this fill a gap? build on music21's strengths? improve usability? -->
+
+
+**Feature summary**
+
+
+**Proposed implementation**
+
+```
+Optionally, describe the implementation or the interface, in code or pseudocode.
+```
+
+**Intent**
+<!-- Please check one or more boxes -->
+
+[ ] I plan on implementing this myself.
+[ ] I am willing to pay to have this feature added.
+[ ] I am starting a discussion with the hope that community members will volunteer their time to create this. I understand that individuals work on features of interest to them and that this feature may never be implemented.
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..4d778d930
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,114 @@
+`music21` welcomes contributions such as bug reports, new features, fixes, and
+documentation improvements. The
+[project repository](http://www.github.com/cuthbertLab/music21) is hosted at GitHub.
+
+
+## Resources ##
+
+[Module Documentation and User's Guide](http://web.mit.edu/music21/doc/index.html)
+
+[Developer Reference](http://web.mit.edu/music21/doc/developerReference/index.html)
+
+[Mailing List](https://groups.google.com/forum/#!forum/music21list)
+
+[Code of Conduct](README.md)
+
+
+## Issue Tickets ##
+
+Please use the provided templates for bug reports or feature proposals. For issues
+parsing file formats, (i.e. missing/buggy features not translated to or from that
+format), please add to (or start) a general ticket for all support requests for that format.
+(Some formats are no longer being actively maintained, such as MEI, LilyPond, and MuseData,
+but well-tested patches can be merged.)
+
+Good bug reports clearly state the unexpected behavior and include a short set of
+commands to reproduce the issue. If your example requires loading a file, a two-measure
+file is appropriate; a symphony movement is not--that's a support request. GitHub
+is not a support channel.
+
+Speaking of support, helpful people hang out on the
+[mailing list](https://groups.google.com/forum/#!forum/music21list)
+or Stack Overflow.
+
+
+## Submitting Pull Requests ##
+
+Open an issue to propose a feature or report a bug before raising a pull request.
+You can include a diff or link to your own repo if you've already started some of the work.
+(Changes where the motivation is self-evident, like handling exceptions or increasing
+test coverage, don't need issue tickets.) A draft pull request can also sometimes
+help resolve design questions faster.
+
+If your PR fixes an existing issue, please use a GitHub keyword ("Fixes #NNNN")
+in the body of the PR so that the issue will be closed on merge.
+
+You can run locally the same tests that GitHub Actions will run. Install `pylint`
+and `flake8` and run them against the files you edited, and give the test suite a go,
+also. (Quick start: run `python3 -m music21.test.multiprocessTest`.)
+If you miss something and GitHub Actions pesters you with red "X"s, just
+expand the details to get the line-by-line violations, and push another commit.
+
+Speaking of commits, use incremental commits with descriptive messages rather
+than force-pushing, unless the reviewer asked you to or the approach is changing
+completely. Force-pushing prevents the reviewer from assessing what changed between
+reviews. (If this doesn't sound familiar--don't worry!)
+
+
+## Style Guide ##
+
+`music21` began in Perl. Now it interfaces with the TypeScript package `music21j`.
+For these reasons, as well as for backward compatibility, we adhere to `camelCase`
+naming--rather than PEP8 `snake_case` naming--for public APIs such as method names,
+arguments, and the like.
+
+That said, snake_case is acceptable (even encouraged!) in new contributions
+for internal variables that are not exposed publicly. Screen readers can
+pronounce these names better.
+
+Conventions:
+
+  - `'strings MUST be single-quoted, but "double quotes" are allowed internally'`
+  - variable names:
+    - need to be unambiguous
+    - must begin with lowercase
+    - snake_case is encouraged for private variables
+  - line lengths are capped at 100, but if approaching this limit, look for ways to avoid one-lining
+  - line continuation characters (`\`) are not allowed; use open parentheses
+  - prefer f-strings to `%` or `.format()`
+  - annotating types is encouraged: e.g. `self.counter: int = 0` or `def makeNoises(): -> List['noise.Noise']`
+  - prefer enums to string configurations
+  - methods:
+    - no more than three positional arguments (in addition to `self`)
+    - keyword arguments should be keyword-only by using `*`
+      to consume any other positional arguments: `def makeNoise(self, volume, *, color=noise.Pink):`
+    - avoid generic `**kwargs`; make keywords explicit
+  - use [Sphinx formatting](http://web.mit.edu/music21/doc/developerReference/documenting.html#documenting-modules-and-classes)
+      to link to classes and methods in docstrings
+  - use descriptive pull request titles (rather than GitHub's default "Update pitch.py")
+
+
+## Testing ##
+
+We write doctests and unit tests, and we strive for the total
+test coverage of the project to increase with every pull request. See the
+[developer docs](http://web.mit.edu/music21/doc/developerReference/index.html)
+to dig in to specific topics like adjusting doctests to prevent
+actions we don't want executed when running the general test suite (such as opening
+browser windows or playing music).
+
+Pull requests that increase or improve coverage of existing features are very welcome.
+Coverage reports can be found at [Coveralls](https://coveralls.io/github/cuthbertLab/music21).
+
+For changes to file parsing, please test both import and export (when supported for
+that format), and please increment the most minor version number in `music21.__version__`
+so that cached files will be invalidated. Your tests can use `converter.parse(fp, forceSource=True)`
+so that tests have a chance to fail locally, but in most cases we will ask you to 
+remove this keyword when polishing the patch.
+
+
+## Finally ##
+
+If you're looking for ways to get started, browse the issues board, the Coveralls module
+coverage, or the TODO stubs in an area of the code that interests you.
+Thanks for your interest in contributing to music21. We look forward to seeing your work!
diff --git a/README.md b/README.md
index 2a05172b1..1ddda9911 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,10 @@ is available also on GitHub.
 
 See: https://groups.google.com/forum/#!forum/music21list
 
+## Contributing Guide ##
+
+[Contributing Guide](CONTRIBUTING.md)
+
 ## Community Code of Conduct ##
 
 Music21 encourages contributions, discussions, and usage from all people interested in
diff --git a/documentation/source/developerReference/documenting.rst b/documentation/source/developerReference/documenting.rst
index 324de6c0c..2c22a8a1a 100644
--- a/documentation/source/developerReference/documenting.rst
+++ b/documentation/source/developerReference/documenting.rst
@@ -368,7 +368,7 @@ Documenting Class-Level Methods
 This is the most common type of documentation, and it ensures both excellent
 documentation and doctests. A typical example of source code might look like this::
 
-    class className():
+    class ClassName(base.Music21Object):
 
         [instance variables, __init__, etc.]
 
@@ -376,7 +376,7 @@ documentation and doctests. A typical example of source code might look like thi
             '''
             This is documentation for this method
 
-            >>> myInstance = className()
+            >>> myInstance = ClassName()
             >>> myInstance.myNewMethod(someParameters)
             >>> myUnicorn.someInstanceVariable
             'value'
diff --git a/music21/_version.py b/music21/_version.py
index 420e32879..333fa7463 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, 4)  # can be 4-tuple: (7, 0, 5, 'a2')
+__version_info__ = (7, 0, 5)  # 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/abcFormat/__init__.py b/music21/abcFormat/__init__.py
index 70f183eb5..b6af2fb78 100644
--- a/music21/abcFormat/__init__.py
+++ b/music21/abcFormat/__init__.py
@@ -3235,7 +3235,7 @@ class ABCFile(prebase.ProtoM21Object):
         Open a file for reading
         '''
         # try:
-        self.file = io.open(filename, encoding='utf-8')
+        self.file = io.open(filename, encoding='utf-8')  # pylint: disable=consider-using-with
         # except
         # self.file = io.open(filename, encoding='latin-1')
         self.filename = filename
diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py
index 630ef6709..ed01a56f8 100644
--- a/music21/analysis/discrete.py
+++ b/music21/analysis/discrete.py
@@ -25,7 +25,7 @@ The :class:`music21.analysis.discrete.KrumhanslSchmuckler`
 #     range and key modules in analysis
 
 import unittest
-from typing import Union, List, Any, Tuple, Iterable
+from typing import Union, List, Any, Tuple, Iterable, Optional
 
 from collections import OrderedDict
 from music21 import exceptions21
@@ -935,7 +935,7 @@ keyWeightKeyAnalysisClasses = [KrumhanslSchmuckler,
 # -----------------------------------------------------------------------------
 class Ambitus(DiscreteAnalysis):
     '''
-    An basic analysis method for measuring register.
+    A basic analysis method for measuring register.
 
     >>> ambitusAnalysis = analysis.discrete.Ambitus()
     >>> ambitusAnalysis.identifiers[0]
@@ -945,10 +945,15 @@ class Ambitus(DiscreteAnalysis):
 
     name = 'Ambitus Analysis'
     # provide possible string matches for this processor
-    identifiers = ['ambitus', 'range', 'span']
+    identifiers = ['ambitus', 'span']
 
     def __init__(self, referenceStream=None):
         super().__init__(referenceStream=referenceStream)
+        # Store the min and max Pitch instances for referenceStream
+        # set by getPitchSpan(), which is called by _generateColors()
+        self.minPitchObj: Optional[pitch.Pitch] = None
+        self.maxPitchObj: Optional[pitch.Pitch] = None
+
         self._pitchSpanColors = OrderedDict()
         self._generateColors()
 
@@ -967,16 +972,18 @@ class Ambitus(DiscreteAnalysis):
         2 #16111d
         3 #16121e
         '''
+        minPitch = 0
         if numColors is None:
             if self._referenceStream is not None:
                 # get total range for entire piece
-                minPitch, maxPitch = self.getPitchRanges(self._referenceStream)
+                self.minPitchObj, self.maxPitchObj = self.getPitchSpan(self._referenceStream)
+                maxPitch = int(self.maxPitchObj.ps - self.minPitchObj.ps)
             else:
-                minPitch, maxPitch = 0, 130  # a large default
+                maxPitch = 130  # a large default
         else:  # create minPitch maxPitch
-            minPitch, maxPitch = 0, numColors
+            maxPitch = numColors
 
-        valueRange = maxPitch - minPitch
+        valueRange = maxPitch
         if valueRange == 0:
             valueRange = 1  # avoid float division by zero
         step = 0
@@ -990,10 +997,10 @@ class Ambitus(DiscreteAnalysis):
 
         # environLocal.printDebug([self._pitchSpanColors])
 
-    def getPitchSpan(self, subStream):
+    def getPitchSpan(self, subStream) -> Optional[Tuple[pitch.Pitch, pitch.Pitch]]:
         '''
-        For a given subStream, return the pitch with the minimum and
-        maximum pitch space value found.
+        For a given subStream, return a tuple consisting of the two pitches
+        with the minimum and maximum pitch space value.
 
         This public method may be used by other classes.
 
@@ -1012,7 +1019,25 @@ class Ambitus(DiscreteAnalysis):
         >>> s.append(c)
         >>> p.getPitchSpan(s)
         (<music21.pitch.Pitch A2>, <music21.pitch.Pitch C8>)
+
+        Returns None if the stream contains no pitches.
+
+        >>> s = stream.Stream(note.Rest())
+        >>> p.getPitchSpan(s) is None
+        True
+
+        OMIT_FROM_DOCS
+
+        And with only ChordSymbols:
+
+        >>> s.insert(4, harmony.ChordSymbol('C6'))
+        >>> p.getPitchSpan(s) is None
+        True
+
         '''
+        if subStream is self._referenceStream and self.minPitchObj and self.maxPitchObj:
+            return self.minPitchObj, self.maxPitchObj
+
         justNotes = subStream.recurse().notes
         if not justNotes:
             # need to handle case of no pitches
@@ -1030,65 +1055,21 @@ class Ambitus(DiscreteAnalysis):
                 pitches = [n.pitch]
             psFound += [p.ps for p in pitches]
             pitchesFound.extend(pitches)
-        # in some cases no pitch space values are found due to all rests
+        # in some cases there is stil nothing -- perhaps only ChordSymbols
         if not psFound:
             return None
         # use built-in functions
         minPitchIndex = psFound.index(min(psFound))
         maxPitchIndex = psFound.index(max(psFound))
 
-        return pitchesFound[minPitchIndex], pitchesFound[maxPitchIndex]
-
-    def getPitchRanges(self, subStream) -> Tuple[int, int]:
-        '''
-        For a given subStream, return the smallest .ps difference
-        between any two pitches and the largest difference
-        between any two pitches. This is used to get the
-        smallest and largest ambitus possible in a given work.
-
-        >>> ambitusAnalyzer = analysis.discrete.Ambitus()
-        >>> s = stream.Stream()
-        >>> c = chord.Chord(['a2', 'b4', 'c8'])
-        >>> s.append(c)
-        >>> [int(thisPitch.ps) for thisPitch in ambitusAnalyzer.getPitchSpan(s)]
-        [45, 108]
-        >>> ambitusAnalyzer.getPitchRanges(s)
-        (26, 63)
-
-        >>> s = corpus.parse('bach/bwv66.6')
-        >>> ambitusAnalyzer.getPitchRanges(s)
-        (0, 34)
+        minPitchObj = pitchesFound[minPitchIndex]
+        maxPitchObj = pitchesFound[maxPitchIndex]
 
-        An empty stream has pitch range (0, 0)
-
-        >>> s = stream.Stream()
-        >>> ambitusAnalyzer.getPitchRanges(s)
-        (0, 0)
-        '''
-        ssfn = subStream.flat.notes
+        if subStream is self._referenceStream:
+            self.minPitchObj = minPitchObj
+            self.maxPitchObj = maxPitchObj
 
-        psFound = []
-        for n in ssfn:
-            pitches = []
-            if 'Chord' in n.classes:
-                pitches = n.pitches
-            elif 'Note' in n.classes:
-                pitches = [n.pitch]
-            for p in pitches:
-                psFound.append(p.ps)
-        psFound.sort()
-        psRange = []
-        for i in range(len(psFound) - 1):
-            p1 = psFound[i]
-            for j in range(i + 1, len(psFound)):
-                p2 = psFound[j]
-                # p2 should always be equal or greater than p1
-                psRange.append(p2 - p1)
-
-        if not psRange:
-            return (0, 0)
-        else:
-            return (int(min(psRange)), int(max(psRange)))
+        return minPitchObj, maxPitchObj
 
     def solutionLegend(self, compress=False):
         '''
@@ -1330,14 +1311,14 @@ def analyzeStream(streamObj, *args, **keywords):
 
     >>> analysis.discrete.analyzeStream(s, 'key')
     <music21.key.Key of f# minor>
-    >>> analysis.discrete.analyzeStream(s, 'range')
+    >>> analysis.discrete.analyzeStream(s, 'span')
     <music21.interval.Interval m21>
 
 
     Note that the same results can be obtained by calling "analyze" directly on the stream object:
     >>> s.analyze('key')
     <music21.key.Key of f# minor>
-    >>> s.analyze('range')
+    >>> s.analyze('span')
     <music21.interval.Interval m21>
 
     '''
@@ -1348,6 +1329,11 @@ def analyzeStream(streamObj, *args, **keywords):
     if args:
         method = args[0]
 
+    if method == 'range':
+        # getPitchRanges() was removed in v7
+        # this synonym is being added for compatibility
+        method = 'span'
+
     match = analysisClassFromMethodName(method)
 
     if match is not None:
@@ -1370,7 +1356,7 @@ def analysisClassFromMethodName(method):
     >>> acfmn = analysis.discrete.analysisClassFromMethodName
     >>> acfmn('aarden')
     <class 'music21.analysis.discrete.AardenEssen'>
-    >>> acfmn('range')
+    >>> acfmn('span')
     <class 'music21.analysis.discrete.Ambitus'>
 
     This one is fundamentally important...
diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py
index e6aacfd57..96c44cca0 100644
--- a/music21/analysis/reduction.py
+++ b/music21/analysis/reduction.py
@@ -908,15 +908,20 @@ class Test(unittest.TestCase):
         self.assertEqual(len(post.parts[0].flat.notes), 3)
         # post.parts[0].show('t')
 
-        match = [(repr(e), e.offset, e.duration.quarterLength)
-            for e in post.parts[0].getElementsByClass('Measure').stream()[0:3].flat.notesAndRests]
+        three_measures = post.parts.first()[stream.Measure][:3]
+        new_stream = stream.Stream()
+        for m in three_measures:
+            new_stream.append(m)
+        flat_stream = new_stream.flat
+        match = [(repr(e), e.offset, e.duration.quarterLength) for e in flat_stream.notesAndRests]
+        self.maxDiff = None
         self.assertEqual(match,
-                         [('<music21.note.Rest rest>', 0.0, 1.0),
+                         [('<music21.note.Rest quarter>', 0.0, 1.0),
                           ('<music21.note.Note F#>', 1.0, 1.0),
-                          ('<music21.note.Rest rest>', 2.0, 1.0),
+                          ('<music21.note.Rest quarter>', 2.0, 1.0),
                           ('<music21.note.Note C#>', 3.0, 1.0),
-                          ('<music21.note.Rest rest>', 5.0, 1.0),
-                          ('<music21.note.Note G#>', 6.0, 1.0)])
+                          ('<music21.note.Rest quarter>', 4.0, 1.0),
+                          ('<music21.note.Note G#>', 5.0, 1.0)])
 
         # test that lyric is found
         self.assertEqual(post.parts[0].flat.notes[0].lyric, 'fromBass')
@@ -1315,4 +1320,3 @@ _DOC_ORDER = []
 if __name__ == '__main__':
     import music21
     music21.mainTest(Test)  # , runTest='testPartReductionSchoenberg')
-
diff --git a/music21/audioSearch/__init__.py b/music21/audioSearch/__init__.py
index ef7fb70a3..1cc74a8c3 100644
--- a/music21/audioSearch/__init__.py
+++ b/music21/audioSearch/__init__.py
@@ -668,7 +668,7 @@ def joinConsecutiveIdenticalPitches(detectedPitchObjects):
     >>> len(notesList)
     24
     >>> print(notesList)
-    [<music21.note.Rest rest>, <music21.note.Note C>, <music21.note.Note C>,
+    [<music21.note.Rest quarter>, <music21.note.Note C>, <music21.note.Note C>,
      <music21.note.Note D>, <music21.note.Note E>, <music21.note.Note F>,
      <music21.note.Note G>, <music21.note.Note A>, <music21.note.Note B>,
      <music21.note.Note C>, ...]
@@ -836,7 +836,7 @@ def notesAndDurationsToStream(
         {2.0} <music21.note.Note B>
         {2.5} <music21.note.Note F#>
         {4.0} <music21.note.Note C>
-        {4.25} <music21.note.Rest rest>
+        {4.25} <music21.note.Rest quarter>
     '''
     # rounding lengths
     p2 = stream.Part()
@@ -920,7 +920,8 @@ def decisionProcess(
     >>> beginningData = []
     >>> lengthData = []
     >>> for i in range(4):
-    ...     scNotes = scoreStream[i * hop + 1:i * hop + tn_recording + 1]
+    ...     excerpt = scoreStream[i * hop + 1:i * hop + tn_recording + 1]
+    ...     scNotes = stream.Part(excerpt)
     ...     name = str(i)
     ...     beginningData.append(i * hop + 1)
     ...     lengthData.append(tn_recording)
diff --git a/music21/audioSearch/scoreFollower.py b/music21/audioSearch/scoreFollower.py
index 86557c2a2..4ec36f464 100644
--- a/music21/audioSearch/scoreFollower.py
+++ b/music21/audioSearch/scoreFollower.py
@@ -17,6 +17,7 @@ from time import time
 
 from music21 import scale
 from music21 import search
+from music21 import stream
 
 from music21 import environment
 _MOD = 'audioSearch.scoreFollower'
@@ -160,7 +161,8 @@ class ScoreFollower:
             detectedPitchObjects)
         self.silencePeriodDetection(notesList)
         environLocal.printDebug('made it to here...')
-        scNotes = self.scoreStream[self.lastNotePosition:self.lastNotePosition + len(notesList)]
+        excerpt = self.scoreStream[self.lastNotePosition:self.lastNotePosition + len(notesList)]
+        scNotes = stream.Part(excerpt)
         # print('1')
         transcribedScore, self.qle = audioSearch.notesAndDurationsToStream(
             notesList,
@@ -501,7 +503,8 @@ class ScoreFollower:
                              - math.ceil(tn_window / hop))
 
         for i in range(iterations):
-            scNotes = scoreStream[i * hop + 1:i * hop + tn_recording + 1]
+            excerpt = scoreStream[i * hop + 1:i * hop + tn_recording + 1]
+            scNotes = stream.Part(excerpt)
             name = str(i)
             beginningData.append(i * hop + 1)
             lengthData.append(tn_recording)
diff --git a/music21/base.py b/music21/base.py
index 7e59f8f62..262e04b42 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.4'
+'7.0.5'
 
 Alternatively, after doing a complete import, these classes are available
 under the module "base":
diff --git a/music21/braille/segment.py b/music21/braille/segment.py
index 8107c82ae..424adf6a3 100644
--- a/music21/braille/segment.py
+++ b/music21/braille/segment.py
@@ -196,11 +196,11 @@ class BrailleElementGrouping(ProtoM21Object):
         >>> bg.append(note.Note('F4'))
         >>> bg
         <music21.braille.segment.BrailleElementGrouping [<music21.note.Note C>,
-            <music21.note.Note D>, <music21.note.Rest rest>, <music21.note.Note F>]>
+            <music21.note.Note D>, <music21.note.Rest quarter>, <music21.note.Note F>]>
         >>> print(bg)
         <music21.note.Note C>
         <music21.note.Note D>
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
         <music21.note.Note F>
 
         These are the defaults and they are shared across all objects...
@@ -1504,9 +1504,9 @@ def findSegments(music21Part, **partKeywords):
     ===
     Measure 15, Note Grouping 1:
     <music21.note.Note C>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
     <music21.note.Note F>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
     ===
     Measure 16, Note Grouping 1:
     <music21.note.Note A->
@@ -2233,7 +2233,7 @@ def splitNoteGrouping(noteGrouping, beatDivisionOffset=0):
 
     >>> right
     <music21.braille.segment.BrailleElementGrouping
-        [<music21.note.Rest rest>, <music21.note.Note E>]>
+        [<music21.note.Rest quarter>, <music21.note.Note E>]>
 
 
     Now split one beat division earlier than it should be.  For 2/2 that means
@@ -2245,7 +2245,7 @@ def splitNoteGrouping(noteGrouping, beatDivisionOffset=0):
         [<music21.note.Note C>]>
     >>> right
     <music21.braille.segment.BrailleElementGrouping
-        [<music21.note.Note D>, <music21.note.Rest rest>, <music21.note.Note E>]>
+        [<music21.note.Note D>, <music21.note.Rest quarter>, <music21.note.Note E>]>
     '''
     music21Measure = stream.Measure()
     for brailleElement in noteGrouping:
diff --git a/music21/capella/fromCapellaXML.py b/music21/capella/fromCapellaXML.py
index 2dab05992..896f5ca36 100644
--- a/music21/capella/fromCapellaXML.py
+++ b/music21/capella/fromCapellaXML.py
@@ -120,8 +120,8 @@ class CapellaImporter:
         Returns self.xmlText
         '''
         self.zipFilename = str(filename)
-        zipFileHandle = zipfile.ZipFile(str(filename), 'r')
-        xmlText = zipFileHandle.read('score.xml')
+        with zipfile.ZipFile(str(filename), 'r') as zipFileHandle:
+            xmlText = zipFileHandle.read('score.xml')
         self.xmlText = xmlText
         return xmlText
 
@@ -414,7 +414,7 @@ class CapellaImporter:
         >>> restElement = ci.domElementFromText('<rest><duration base="1/2"/></rest>')
         >>> r = ci.restFromRest(restElement)
         >>> r
-        <music21.note.Rest rest>
+        <music21.note.Rest half>
         >>> r.duration.type
         'half'
         '''
diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py
index c5da5e381..7be27c3f9 100644
--- a/music21/converter/__init__.py
+++ b/music21/converter/__init__.py
@@ -134,7 +134,8 @@ class ArchiveManager:
             if self.fp.suffix in ('.mxl', '.md'):
                 # try to open it, as some mxl files are not zips
                 try:
-                    unused = zipfile.ZipFile(self.fp, 'r')
+                    with zipfile.ZipFile(self.fp, 'r') as unused:
+                        pass
                 except zipfile.BadZipfile:
                     return False
                 return True
@@ -150,10 +151,9 @@ class ArchiveManager:
         '''
         post = []
         if self.archiveType == 'zip':
-            f = zipfile.ZipFile(self.fp, 'r')
-            for subFp in f.namelist():
-                post.append(subFp)
-            f.close()
+            with zipfile.ZipFile(self.fp, 'r') as f:
+                for subFp in f.namelist():
+                    post.append(subFp)
         return post
 
     def getData(self, name=None, dataFormat='musicxml'):
@@ -168,8 +168,12 @@ class ArchiveManager:
         if self.archiveType != 'zip':
             raise ArchiveManagerException(f'no support for extension: {self.archiveType}')
 
-        f = zipfile.ZipFile(self.fp, 'r')
+        with zipfile.ZipFile(self.fp, 'r') as f:
+            post = self._extractContents(f, name, dataFormat)
+
+        return post
 
+    def _extractContents(self, f: zipfile.ZipFile, name=None, dataFormat='musicxml'):
         if name is None and dataFormat == 'musicxml':  # try to auto-harvest
             # will return data as a string
             # note that we need to read the META-INF/container.xml file
@@ -232,8 +236,6 @@ class ArchiveManager:
                 # post.append(f.read(subFp, 'U'))
                 # msg.append('\n/END\n')
 
-        f.close()
-
         return post
 
 
@@ -1358,12 +1360,8 @@ class TestExternal(unittest.TestCase):  # pragma: no cover
         urlBase = 'http://kern.ccarh.org/cgi-bin/ksdata?l=users/craig/classical/'
         urlB = urlBase + 'schubert/piano/d0576&file=d0576-06.krn&f=kern'
         urlC = urlBase + 'bach/cello&file=bwv1007-01.krn&f=xml'
-        for url in [urlB, urlC]:
-            try:
-                unused_post = parseURL(url)
-            except:
-                print(url)
-                raise
+        unused_post = parseURL(urlB)
+        unused_post = parseURL(urlC)
 
     def testFreezer(self):
         from music21 import corpus
@@ -1949,8 +1947,8 @@ class Test(unittest.TestCase):
     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'
+        urlBase = 'https://raw.githubusercontent.com/craigsapp/chopin-preludes/'
+        url = urlBase + 'f8fb01f09d717e84929fb8b2950f96dd6bc05686/kern/prelude28-20.krn'
 
         e = environment.Environment()
         e['autoDownload'] = 'allow'
diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py
index d5d719c2b..1dce94d11 100644
--- a/music21/converter/subConverters.py
+++ b/music21/converter/subConverters.py
@@ -266,36 +266,28 @@ class SubConverter:
         else:
             writeFlags = 'wb'
 
-        if self.codecWrite is False:
-            if hasattr(fp, 'write'):
-                # is a file-like object
-                f = fp
-            else:
-                fp = common.cleanpath(fp)
-                f = open(fp, writeFlags)
-
+        if self.codecWrite is False and isinstance(dataStr, bytes):
             try:
-                if isinstance(dataStr, bytes):
-                    f.write(dataStr.decode('utf-8'))
-
-                else:
-                    f.write(dataStr)
-            except UnicodeEncodeError:
-                f.close()
-                f = io.open(fp, mode=writeFlags, encoding=self.stringEncoding)
+                dataStr = dataStr.decode('utf-8')
+            except UnicodeDecodeError:
+                # Reattempt below with self.stringEncoding
+                self.codecWrite = True
+                # Close file if already open, because we need to reopen with encoding
+                if hasattr(fp, 'write'):
+                    fp.close()
+
+        if not hasattr(fp, 'write'):
+            fp = common.cleanpath(fp)
+            with open(fp,
+                      mode=writeFlags,
+                      encoding=self.stringEncoding if self.codecWrite else None
+                      ) as f:
                 f.write(dataStr)
-                f.close()
-
-            except TypeError as te:
-                raise SubConverterException(f'Could not convert {dataStr!r} : {te!r}')
         else:
-            if hasattr(fp, 'write'):
-                # is a file-like object
-                f = fp
-            else:
-                f = io.open(fp, mode=writeFlags, encoding=self.stringEncoding)
+            # file-like object
             f.write(dataStr)
             f.close()
+
         return fp
 
 
@@ -658,13 +650,13 @@ class ConverterHumdrum(SubConverter):
             {0.0} <music21.humdrum.spineParser.MiscTandem **kern>
             {0.0} <music21.stream.Measure 1 offset=0.0>
                 {0.0} <music21.meter.TimeSignature 2/4>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest 1/6ql>
                 {0.1667} <music21.note.Note G#>
                 {0.3333} <music21.note.Note F#>
                 {0.5} <music21.note.Note E>
                 {0.6667} <music21.note.Note C#>
                 {0.8333} <music21.note.Note F>
-                {1.0} <music21.note.Rest rest>
+                {1.0} <music21.note.Rest 1/6ql>
                 {1.1667} <music21.note.Note D>
                 {1.3333} <music21.note.Note E->
                 {1.5} <music21.note.Note G>
@@ -727,7 +719,7 @@ class ConverterTinyNotation(SubConverter):
             {0.0} <music21.clef.TrebleClef>
             {0.0} <music21.meter.TimeSignature 3/4>
             {0.0} <music21.note.Note E>
-            {1.0} <music21.note.Rest rest>
+            {1.0} <music21.note.Rest quarter>
             {2.0} <music21.note.Note F#>
         {3.0} <music21.stream.Measure 2 offset=3.0>
             {0.0} <music21.note.Note G>
diff --git a/music21/duration.py b/music21/duration.py
index 051d1f7a3..00920f9ec 100644
--- a/music21/duration.py
+++ b/music21/duration.py
@@ -653,11 +653,11 @@ def quarterConversion(qLen: OffsetQLIn) -> QuarterLengthConversion:
     # Tuplets...
     qLen = opFrac(qLen)
     # try match to type, get next lowest for next part...
-    closestSmallerType, unused_match = quarterLengthToClosestType(qLen)
     try:
+        closestSmallerType, unused_match = quarterLengthToClosestType(qLen)
         nextLargerType(closestSmallerType)
     except DurationException:
-        # too big...
+        # too big or too small
         return QuarterLengthConversion((DurationTuple(type='inexpressible',
                                                       dots=0,
                                                       quarterLength=qLen),), None)
@@ -697,7 +697,13 @@ def quarterConversion(qLen: OffsetQLIn) -> QuarterLengthConversion:
 
     # 8 tied components was not enough.
     # last resort: put one giant tuplet over it.
-    tuplet, component = quarterLengthToNonPowerOf2Tuplet(qLen)
+    try:
+        tuplet, component = quarterLengthToNonPowerOf2Tuplet(qLen)
+    except DurationException:
+        # Failures include 1/72
+        return QuarterLengthConversion((DurationTuple(type='inexpressible',
+                                                      dots=0,
+                                                      quarterLength=qLen),), None)
     return QuarterLengthConversion((component,), tuplet)
 
 
@@ -2119,7 +2125,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
 
         For an 'inexpressible' duration, the opposite of consolidate is
         to set the duration's quarterLength to itself.  It won't necessarily
-        return to the original components, but it will (usually? always?)
+        return to the original components, but it will usually
         create something that can be notated.
 
         >>> a.quarterLength = a.quarterLength
@@ -2166,7 +2172,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
 
         >>> gd = d.getGraceDuration()
         >>> gd
-        <music21.duration.GraceDuration unlinked type:zero quarterLength:0.0>
+        <music21.duration.GraceDuration unlinked type:complex quarterLength:0.0>
         >>> gd.quarterLength
         0.0
         >>> gd.components
@@ -2192,6 +2198,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin):
             newComponents.append(DurationTuple(c.type, c.dots, 0.0))
         gd.components = newComponents  # set new components
         gd.linked = False
+        gd.type = self.type
         gd.quarterLength = 0.0
         return gd
 
@@ -3649,6 +3656,16 @@ class Test(unittest.TestCase):
             Duration(fractions.Fraction(6 / 7)).fullName
         )
 
+    def testTinyDuration(self):
+        # e.g. delta from chordify: 1/9 - 1/8 = 1/72
+        # exercises quarterLengthToNonPowerOf2Tuplet()
+        d = Duration(1 / 72)
+        self.assertEqual(d.type, 'inexpressible')
+
+        # this failure happens earlier in quarterConversion()
+        d = Duration(1 / 2049)
+        self.assertEqual(d.type, 'inexpressible')
+
 
 # -------------------------------------------------------------------------------
 # define presented order in documentation
diff --git a/music21/features/jSymbolic.py b/music21/features/jSymbolic.py
index cdd885220..8997c2a1f 100644
--- a/music21/features/jSymbolic.py
+++ b/music21/features/jSymbolic.py
@@ -24,6 +24,7 @@ from math import isclose
 from music21 import base
 from music21 import exceptions21
 from music21.features import base as featuresModule
+from music21.instrument import Instrument
 
 from music21 import environment
 _MOD = 'features.jSymbolic'
@@ -3322,6 +3323,20 @@ class PitchedInstrumentsPresentFeature(featuresModule.FeatureExtractor):
      1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+
+    Default instruments will lack a `.midiProgram`, so they raise exceptions:
+
+    >>> i = instrument.Instrument()
+    >>> i.midiProgram is None
+    True
+    >>> s2 = stream.Stream()
+    >>> s2.append(i)
+    >>> s2.append(note.Note())
+    >>> fe2 = features.jSymbolic.PitchedInstrumentsPresentFeature(s2)
+    >>> fe2.extract()
+    Traceback (most recent call last):
+    music21.features.jSymbolic.JSymbolicFeatureException:
+    <music21.instrument.Instrument ''> lacks a midiProgram
     '''
     id = 'I1'
 
@@ -3342,18 +3357,16 @@ class PitchedInstrumentsPresentFeature(featuresModule.FeatureExtractor):
         s = self.data['partitionByInstrument']
         # each part has content for each instrument
         # count = 0
-        if s is not None:
-            for p in s.parts:
-                # always one instrument
-                x = p.getElementsByClass('Instrument')
-                if x:
-                    i = x[0]
-                    if p.recurse().notes:
-                        self.feature.vector[i.midiProgram] = 1
-                else:
-                    pass
-        else:
+        if not s:
             raise JSymbolicFeatureException('input lacks instruments')
+        for p in s.parts:
+            # always one instrument
+            i = p.getElementsByClass(Instrument).first()
+            if p.recurse().notes:
+                if i.midiProgram is None:
+                    iStr = str(i) or repr(i)
+                    raise JSymbolicFeatureException(f'{iStr} lacks a midiProgram')
+                self.feature.vector[i.midiProgram] = 1
 
 
 class UnpitchedInstrumentsPresentFeature(featuresModule.FeatureExtractor):
@@ -3408,6 +3421,13 @@ class NotePrevalenceOfPitchedInstrumentsFeature(
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 
+    `.midiProgram` cannot be None:
+
+    >>> s1.getInstruments().first().midiProgram = None
+    >>> fe2 = features.jSymbolic.NotePrevalenceOfPitchedInstrumentsFeature(s1)
+    >>> fe2.extract()
+    Traceback (most recent call last):
+    music21.features.jSymbolic.JSymbolicFeatureException: Acoustic Guitar lacks a midiProgram
     '''
     id = 'I3'
 
@@ -3434,9 +3454,12 @@ class NotePrevalenceOfPitchedInstrumentsFeature(
             raise JSymbolicFeatureException('input lacks notes')
         for p in s.parts:
             # always one instrument
-            i = p.getElementsByClass('Instrument').first()
+            i = p.getElementsByClass(Instrument).first()
             pNotes = p.recurse().notes
             if pNotes:
+                if i.midiProgram is None:
+                    iStr = str(i) or repr(i)
+                    raise JSymbolicFeatureException(f'{iStr} lacks a midiProgram')
                 self.feature.vector[i.midiProgram] = len(pNotes) / total
 
 
@@ -3539,7 +3562,7 @@ class VariabilityOfNotePrevalenceOfPitchedInstrumentsFeature(
         coll = []
         for p in s.parts:
             # always one instrument
-            i = p.getElementsByClass('Instrument').first()
+            i = p.getElementsByClass(Instrument).first()
             pNotes = p.recurse().notes
             if pNotes:
                 coll.append(len(pNotes) / total)
@@ -3689,7 +3712,7 @@ class InstrumentFractionFeature(featuresModule.FeatureExtractor):
         if not total:
             raise JSymbolicFeatureException('input lacks notes')
         for p in s.parts:
-            i = p.getElementsByClass('Instrument').first()
+            i = p.getElementsByClass(Instrument).first()
             if i.midiProgram in self._targetPrograms:
                 count += len(p.flat.notes)
         self.feature.vector[0] = count / total
diff --git a/music21/figuredBass/realizer.py b/music21/figuredBass/realizer.py
index 532b680c1..9a7663a38 100644
--- a/music21/figuredBass/realizer.py
+++ b/music21/figuredBass/realizer.py
@@ -263,7 +263,7 @@ class FiguredBassLine:
 
         >>> from music21 import corpus
         >>> sBach = corpus.parse('bach/bwv307')
-        >>> sBach['bass'].measure(0).show('text')
+        >>> sBach.parts.last().measure(0).show('text')
         {0.0} ...
         {0.0} <music21.clef.BassClef>
         {0.0} <music21.key.Key of B- major>
@@ -271,7 +271,7 @@ class FiguredBassLine:
         {0.0} <music21.note.Note B->
         {0.5} <music21.note.Note C>
 
-        >>> fbLine = realizer.figuredBassFromStream(sBach['bass'])
+        >>> fbLine = realizer.figuredBassFromStream(sBach.parts.last())
         >>> fbLine.generateBassLine().measure(1).show('text')
         {0.0} <music21.clef.BassClef>
         {0.0} <music21.key.KeySignature of 2 flats>
diff --git a/music21/freezeThaw.py b/music21/freezeThaw.py
index 233ce187a..86b9eba5f 100644
--- a/music21/freezeThaw.py
+++ b/music21/freezeThaw.py
@@ -553,7 +553,7 @@ class StreamFreezer(StreamFreezeThawBase):
         >>> n2.duration.type = 'whole'
         >>> m2.append(n2)
         >>> s2.append(m2)
-        >>> v = variant.Variant(s2)
+        >>> v = variant.Variant(s2.elements)
         >>> s.insert(0, v)
         >>> sf = freezeThaw.StreamFreezer(s, fastButUnsafe=True)
         >>> allIds = sf.findActiveStreamIdsInHierarchy()
@@ -918,9 +918,8 @@ class StreamThawer(StreamFreezeThawBase):
             directory = environLocal.getRootTempDir()
             fp = directory / fp
 
-        f = open(fp, 'rb')
-        fileData = f.read()  # TODO: do not read entire file
-        f.close()
+        with open(fp, 'rb') as f:
+            fileData = f.read()  # TODO: do not read entire file
 
         fmt = self.parseOpenFmt(fileData)
         if fmt == 'pickle':
@@ -946,9 +945,8 @@ class StreamThawer(StreamFreezeThawBase):
             common.restorePathClassesAfterUnpickling()
         elif fmt == 'jsonpickle':
             import jsonpickle
-            f = open(fp, 'r')
-            data = f.read()
-            f.close()
+            with open(fp, 'r') as f:
+                data = f.read()
             storage = jsonpickle.decode(data)
             self.stream = self.unpackStream(storage)
         else:  # pragma: no cover
diff --git a/music21/instrument.py b/music21/instrument.py
index b623867f9..dd0e3b22f 100644
--- a/music21/instrument.py
+++ b/music21/instrument.py
@@ -2123,11 +2123,11 @@ def partitionByInstrument(streamObj):
             {0.0} <music21.meter.TimeSignature 4/4>
             {0.0} <music21.note.Note C>
             {1.0} <music21.note.Note D>
-            {2.0} <music21.note.Rest rest>
+            {2.0} <music21.note.Rest quarter>
             {3.0} <music21.note.Note F#>
         {4.0} <music21.stream.Measure 2 offset=4.0>
             {0.0} <music21.note.Note G#>
-            {1.0} <music21.note.Rest rest>
+            {1.0} <music21.note.Rest half>
             {3.0} <music21.note.Note C>
         {8.0} <music21.stream.Measure 3 offset=8.0>
             {0.0} <music21.note.Note C>
@@ -2137,7 +2137,7 @@ def partitionByInstrument(streamObj):
             {0.0} <music21.instrument.AltoSaxophone 'Alto Saxophone'>
             {0.0} <music21.clef.TrebleClef>
             {0.0} <music21.meter.TimeSignature 4/4>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest half>
             {2.0} <music21.note.Note E>
             {3.0} <music21.note.Note F>
         {4.0} <music21.stream.Measure 2 offset=4.0>
@@ -2153,9 +2153,9 @@ def partitionByInstrument(streamObj):
             {0.0} <music21.note.Note C#>
             {1.0} <music21.note.Note D#>
             {2.0} <music21.note.Note E#>
-            {3.0} <music21.note.Rest rest>
+            {3.0} <music21.note.Rest quarter>
         {4.0} <music21.stream.Measure 2 offset=4.0>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest quarter>
             {1.0} <music21.note.Note A#>
             {2.0} <music21.note.Note B#>
             {3.0} <music21.note.Note C#>
@@ -2165,6 +2165,7 @@ def partitionByInstrument(streamObj):
 
     TODO: parts should be in Score Order. Coincidence that this almost works.
     TODO: use proper recursion to make a copy of the stream.
+    TODO: final barlines should be aligned.
     '''
     from music21 import stream
 
diff --git a/music21/key.py b/music21/key.py
index c3086e8e6..0dd523f0c 100644
--- a/music21/key.py
+++ b/music21/key.py
@@ -1095,7 +1095,7 @@ class Key(KeySignature, scale.DiatonicScale):
         alternateInterpretations list has
         been filled from the use of a KeyWeightKeyAnalysis subclass.
 
-        >>> littlePiece = converter.parse('tinyNotation: 4/4 c4 d e f g a b cc ee gg ee cc')
+        >>> littlePiece = converter.parse("tinyNotation: 4/4 c4 d e f g a b c' e' g' e' c'")
         >>> k = littlePiece.analyze('key')
         >>> k
         <music21.key.Key of C major>
diff --git a/music21/lily/translate.py b/music21/lily/translate.py
index a7e4744c9..648ff2ab6 100644
--- a/music21/lily/translate.py
+++ b/music21/lily/translate.py
@@ -198,16 +198,16 @@ class LilypondConverter:
         LILYEXEC = self.findLilyExec()
         command = [LILYEXEC, '--version']
         try:
-            proc = subprocess.Popen(command, stdout=subprocess.PIPE)
-        except OSError:  # pragma: no cover
+            with subprocess.Popen(command, stdout=subprocess.PIPE) as proc:
+                stdout, unused = proc.communicate()
+                stdout = stdout.decode(encoding='utf-8')
+                versionString = stdout.split()[2]
+                versionPieces = versionString.split('.')
+        except OSError as exc:  # pragma: no cover
             raise LilyTranslateException(
                 'Cannot find a copy of Lilypond installed on your system. '
                 + 'Please be sure it is installed. And that your '
-                + "environment.UserSettings()['lilypondPath'] is set to find it.")
-        stdout, unused = proc.communicate()
-        stdout = stdout.decode(encoding='utf-8')
-        versionString = stdout.split()[2]
-        versionPieces = versionString.split('.')
+                + "environment.UserSettings()['lilypondPath'] is set to find it.") from exc
 
         self.majorVersion = versionPieces[0]
         self.minorVersion = versionPieces[1]
@@ -1291,9 +1291,6 @@ class LilypondConverter:
                         simpleElementParts.append('? ')
             else:
                 simpleElementParts.append('s ')
-
-        elif 'SpacerRest' in c:
-            simpleElementParts.append('s ')
         elif 'Rest' in c:
             if noteOrRest.hasStyleInformation and noteOrRest.style.hideObjectOnPrint:
                 simpleElementParts.append('s ')
@@ -1860,7 +1857,7 @@ class LilypondConverter:
         >>> for s in [ s1, s2, s3, s4, s5]:
         ...     s.makeMeasures(inPlace=True)
 
-        >>> activeSite = stream.Part(s5)
+        >>> activeSite = stream.Part(s5.elements)
 
         >>> v1 = variant.Variant()
         >>> for el in s1:
@@ -1868,7 +1865,8 @@ class LilypondConverter:
         >>> v1.replacementDuration = 4.0
 
         >>> v2 = variant.Variant()
-        >>> sp2 = note.SpacerRest()
+        >>> sp2 = note.Rest()
+        >>> sp2.style.hideObjectOnPrint = True
         >>> sp2.duration.quarterLength = 4.0
         >>> v2.replacementDuration = 4.0
         >>> v2.append(sp2)
@@ -1876,7 +1874,8 @@ class LilypondConverter:
         ...     v2.append(el)
 
         >>> v3 = variant.Variant()
-        >>> sp3 = note.SpacerRest()
+        >>> sp3 = note.Rest()
+        >>> sp3.style.hideObjectOnPrint = True
         >>> sp3.duration.quarterLength = 8.0
         >>> v3.replacementDuration = 4.0
         >>> v3.append(sp3)
@@ -1884,7 +1883,8 @@ class LilypondConverter:
         ...     v3.append(el)
 
         >>> v4 = variant.Variant()
-        >>> sp4 = note.SpacerRest()
+        >>> sp4 = note.Rest()
+        >>> sp4.style.hideObjectOnPrint = True
         >>> sp4.duration.quarterLength = 16.0
         >>> v4.replacementDuration = 4.0
         >>> v4.append(sp4)
@@ -1947,7 +1947,7 @@ class LilypondConverter:
 
         def findOffsetOfFirstNonSpacerElement(inputStream):
             for el in inputStream:
-                if 'SpacerRest' in el.classes:
+                if 'Rest' in el.classes and el.style.hideObjectOnPrint:
                     pass
                 else:
                     return inputStream.elementOffset(el)
@@ -2004,7 +2004,8 @@ class LilypondConverter:
 
             # make spacer with spacerDuration and append
             if spacerDuration > 0.0:
-                spacer = note.SpacerRest()
+                spacer = note.Rest()
+                spacer.style.hideObjectOnPrint = True
                 spacer.duration.quarterLength = spacerDuration
                 # noinspection PyTypeChecker
                 lySpacer = self.lySimpleMusicFromNoteOrRest(spacer)
@@ -2069,11 +2070,11 @@ class LilypondConverter:
 
         >>> pStream = converter.parse('tinynotation: 4/4 a4 b c d   e4 f g a')
         >>> pStream.makeMeasures(inPlace=True)
-        >>> p = stream.Part(pStream)
+        >>> p = stream.Part(pStream.elements)
         >>> p.id = 'p1'
         >>> vStream = converter.parse('tinynotation: 4/4 a4. b8 c4 d')
         >>> vStream.makeMeasures(inPlace=True)
-        >>> v = variant.Variant(vStream)
+        >>> v = variant.Variant(vStream.elements)
         >>> v.groups = ['london']
         >>> p.insert(0.0, v)
         >>> lpc = lily.translate.LilypondConverter()
@@ -2133,7 +2134,9 @@ class LilypondConverter:
 
         musicList = []
 
-        varFilter = variantObject.getElementsByClass('SpacerRest')
+        varFilter = [r for r in variantObject.getElementsByClass('Rest')
+                     if r.style.hideObjectOnPrint]
+
         if varFilter:
             spacer = varFilter[0]
             spacerDur = spacer.duration.quarterLength
@@ -2218,7 +2221,7 @@ class LilypondConverter:
 
 
         >>> c = converter.parse('tinynotation: 3/4 C4 D E F2.')
-        >>> v = variant.Variant(c)
+        >>> v = variant.Variant(c.elements)
         >>> lpc = lily.translate.LilypondConverter()
         >>> lySequentialMusicOut = lpc.lySequentialMusicFromStream(v)
         >>> lySequentialMusicOut
diff --git a/music21/mei/base.py b/music21/mei/base.py
index eb0a0561c..33a86629c 100644
--- a/music21/mei/base.py
+++ b/music21/mei/base.py
@@ -2330,13 +2330,16 @@ def spaceFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
     <space>  A placeholder used to fill an incomplete measure, layer, etc. most often so that the
     combined duration of the events equals the number of beats in the measure.
 
+    Returns a Rest element with hideObjectOnPrint = True
+
     In MEI 2013: pg.440 (455 in PDF) (MEI.shared module)
     '''
     # NOTE: keep this in sync with restFromElement()
 
     theDuration = _qlDurationFromAttr(elem.get('dur'))
     theDuration = makeDuration(theDuration, int(elem.get('dots', 0)))
-    theSpace = note.SpacerRest(duration=theDuration)
+    theSpace = note.Rest(duration=theDuration)
+    theSpace.style.hideObjectOnPrint = True
 
     if elem.get(_XMLID) is not None:
         theSpace.id = elem.get(_XMLID)
diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py
index dddd4d146..38876453b 100644
--- a/music21/metadata/__init__.py
+++ b/music21/metadata/__init__.py
@@ -1266,12 +1266,11 @@ class RichMetadata(Metadata):
         self.pitchHighest = None
         self.pitchLowest = None
         analysisObject = discrete.Ambitus(streamObj)
-        psRange = analysisObject.getPitchSpan(streamObj)
-        if psRange is not None:
+        if analysisObject.minPitchObj is not None and analysisObject.maxPitchObj is not None:
             # may be none if no pitches are stored
             # presently, these are numbers; convert to pitches later
-            self.pitchLowest = psRange[0].nameWithOctave
-            self.pitchHighest = psRange[1].nameWithOctave
+            self.pitchLowest = analysisObject.minPitchObj.nameWithOctave
+            self.pitchHighest = analysisObject.maxPitchObj.nameWithOctave
         ambitusInterval = analysisObject.getSolution(streamObj)
         self.ambitus = AmbitusShort(semitones=ambitusInterval.semitones,
                                     diatonic=ambitusInterval.diatonic.simpleName,
diff --git a/music21/metadata/caching.py b/music21/metadata/caching.py
index 86fd7339d..80ea12b5e 100644
--- a/music21/metadata/caching.py
+++ b/music21/metadata/caching.py
@@ -328,11 +328,10 @@ class JobProcessor:
 
         '''
         processCount = processCount or common.cpus()
-        if processCount < 1:
-            processCount = 1
+        processCount = max(processCount, 1)
+        # do not start more processes than jobs...
         remainingJobs = len(jobs)
-        if processCount > remainingJobs:  # do not start more processes than jobs...
-            processCount = remainingJobs
+        processCount = min(processCount, remainingJobs)
 
         environLocal.printDebug(
             f'Processing {remainingJobs} jobs in parallel, with {processCount} processes.')
diff --git a/music21/meter/tools.py b/music21/meter/tools.py
index d1eafd0a4..2ea79b17e 100644
--- a/music21/meter/tools.py
+++ b/music21/meter/tools.py
@@ -48,7 +48,7 @@ def slashToTuple(value: str) -> Optional[MeterTerminalTuple]:
     '''
     # split by numbers, include slash
     valueNumbers, valueChars = common.getNumFromStr(value,
-                                                    numbers='0123456789/')
+                                                    numbers='0123456789/.')
     valueNumbers = valueNumbers.strip()  # remove whitespace
     valueChars = valueChars.strip()  # remove whitespace
     if 'slow' in valueChars.lower():
@@ -123,6 +123,10 @@ def slashMixedToFraction(valueSrc: str) -> Tuple[NumDenomTuple, bool]:
     Traceback (most recent call last):
     music21.exceptions21.MeterException: cannot match denominator to numerator in: 3+2+5/8+3/4+2+1+4
 
+    >>> meter.tools.slashMixedToFraction('3.0/4.0')
+    Traceback (most recent call last):
+    music21.exceptions21.TimeSignatureException: Cannot create time signature from "3.0/4.0"
+
     Changed in v7 -- new location and returns a tuple as first value.
     '''
     pre = []
@@ -134,7 +138,8 @@ def slashMixedToFraction(valueSrc: str) -> Tuple[NumDenomTuple, bool]:
         if '/' in part:
             tup = slashToTuple(part)
             if tup is None:
-                raise TimeSignatureException('cannot create time signature from:', valueSrc)
+                raise TimeSignatureException(
+                    f'Cannot create time signature from "{valueSrc}"')
             pre.append([tup.numerator, tup.denominator])
         else:  # its just a numerator
             try:
diff --git a/music21/midi/__init__.py b/music21/midi/__init__.py
index 7e5c6e6fe..efb2f7482 100644
--- a/music21/midi/__init__.py
+++ b/music21/midi/__init__.py
@@ -1544,7 +1544,7 @@ class MidiFile(prebase.ProtoM21Object):
         '''
         if attrib not in ['rb', 'wb']:
             raise MidiException('cannot read or write unless in binary mode, not:', attrib)
-        self.file = open(filename, attrib)
+        self.file = open(filename, attrib)  # pylint: disable=consider-using-with
 
     def openFileLike(self, fileLike):
         '''Assign a file-like object, such as those provided by BytesIO, as an open file object.
diff --git a/music21/midi/translate.py b/music21/midi/translate.py
index 92b403ab5..8d65a27c2 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -1796,22 +1796,22 @@ def midiTrackToStream(
         {0.0} <music21.clef.TrebleClef>
         {0.0} <music21.meter.TimeSignature 4/4>
         {0.0} <music21.note.Note C>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest quarter>
         {2.0} <music21.chord.Chord F3 G#4 C5>
-        {3.0} <music21.note.Rest rest>
+        {3.0} <music21.note.Rest quarter>
     {4.0} <music21.stream.Measure 2 offset=4.0>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest eighth>
         {0.5} <music21.note.Note B->
-        {1.5} <music21.note.Rest rest>
+        {1.5} <music21.note.Rest half>
         {3.5} <music21.chord.Chord D2 A4>
     {8.0} <music21.stream.Measure 3 offset=8.0>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest eighth>
         {0.5} <music21.chord.Chord C#2 B-3 G#6>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest dotted-quarter>
         {2.5} <music21.chord.Chord F#3 A4 C#5>
     {12.0} <music21.stream.Measure 4 offset=12.0>
         {0.0} <music21.chord.Chord F#3 A4 C#5>
-        {2.5} <music21.note.Rest rest>
+        {2.5} <music21.note.Rest dotted-quarter>
         {4.0} <music21.bar.Barline type=final>
     '''
     # environLocal.printDebug(['midiTrackToStream(): got midi track: events',
@@ -2669,7 +2669,7 @@ def midiStringToStream(strData, **keywords):
             {0.0} <music21.clef.TrebleClef>
             {0.0} <music21.meter.TimeSignature 4/4>
             {0.0} <music21.note.Note G>
-            {1.0} <music21.note.Rest rest>
+            {1.0} <music21.note.Rest dotted-half>
             {4.0} <music21.bar.Barline type=final>
     '''
     from music21 import midi as midiModule
diff --git a/music21/musedata/__init__.py b/music21/musedata/__init__.py
index 0c4db7237..e31a5ec77 100644
--- a/music21/musedata/__init__.py
+++ b/music21/musedata/__init__.py
@@ -1449,7 +1449,7 @@ class MuseDataFile(prebase.ProtoM21Object):
     def open(self, fp):
         # self.file = io.open(filename, encoding='utf-8')
 
-        self.file = open(fp, 'rb')
+        self.file = open(fp, 'rb')  # pylint: disable=consider-using-with
         self.filename = fp
 
     def read(self):
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index ea2a14abf..0301cb43f 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -64,12 +64,24 @@ def typeToMusicXMLType(value):
     'long'
     >>> musicxml.m21ToXml.typeToMusicXMLType('quarter')
     'quarter'
+    >>> musicxml.m21ToXml.typeToMusicXMLType('inexpressible')
+    Traceback (most recent call last):
+    music21.musicxml.xmlObjects.MusicXMLExportException:
+    Cannot convert inexpressible durations to MusicXML.
+    >>> musicxml.m21ToXml.typeToMusicXMLType('zero')
+    Traceback (most recent call last):
+    music21.musicxml.xmlObjects.MusicXMLExportException:
+    Cannot convert durations without types to MusicXML.
     '''
     # MusicXML uses long instead of longa
     if value == 'longa':
         return 'long'
     elif value == '2048th':
         raise MusicXMLExportException('Cannot convert "2048th" duration to MusicXML (too short).')
+    elif value == 'inexpressible':
+        raise MusicXMLExportException('Cannot convert inexpressible durations to MusicXML.')
+    elif value == 'zero':
+        raise MusicXMLExportException('Cannot convert durations without types to MusicXML.')
     else:
         return value
 
@@ -723,9 +735,7 @@ class XMLExporterBase:
           <accidental />
           </score-partwise>
         '''
-        commentLength = len(comment)
-        if commentLength > 60:
-            commentLength = 60
+        commentLength = min(len(comment), 60)
         spacerLengthLow = math.floor((60 - commentLength) / 2)
         spacerLengthHigh = math.ceil((60 - commentLength) / 2)
 
@@ -3377,27 +3387,18 @@ class MeasureExporter(XMLExporterBase):
             mxVoice = SubElement(mxNote, 'voice')
             mxVoice.text = str(self.currentVoiceId)
 
-        if d.type != 'zero':
-            mxType = Element('type')
+        mxType = Element('type')
+        if d.isGrace is True and d.type == 'zero':
+            # Default type-less grace durations to eighths
+            mxType.text = 'eighth'
+        else:
             mxType.text = typeToMusicXMLType(d.type)
-            self.setStyleAttributes(mxType, n, 'size', 'noteSize')
-            mxNote.append(mxType)
-            for unused_dotCounter in range(d.dots):
-                SubElement(mxNote, 'dot')
-                # TODO: dot placement...
-
-        elif d.components:
-            mxType = Element('type')
-            mxType.text = typeToMusicXMLType(d.components[0].type)
-            self.setStyleAttributes(mxType, n, 'size', 'noteSize')
-            if (n.hasStyleInformation
-                    and hasattr(n.style, 'noteSize')
-                    and n.style.noteSize is not None):
-                mxType.set('size', n.style.noteSize)
-
-            mxNote.append(mxType)
-            for unused_dotCounter in range(d.components[0].dots):
-                SubElement(mxNote, 'dot')
+
+        self.setStyleAttributes(mxType, n, 'size', 'noteSize')
+        mxNote.append(mxType)
+        for unused_dotCounter in range(d.dots):
+            SubElement(mxNote, 'dot')
+            # TODO: dot placement...
 
         if (hasattr(n, 'pitch')
                 and n.pitch.accidental is not None
diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py
index 7601297c7..35faa2ea5 100644
--- a/music21/musicxml/xmlToM21.py
+++ b/music21/musicxml/xmlToM21.py
@@ -18,6 +18,8 @@ import re
 # import sys
 # import traceback
 import unittest
+
+from math import isclose
 from typing import List, Optional, Dict, Tuple, Set
 
 import xml.etree.ElementTree as ET
@@ -1798,7 +1800,7 @@ class PartParser(XMLParserBase):
 
         >>> measureRest = m.notesAndRests[0]
         >>> measureRest
-        <music21.note.Rest rest>
+        <music21.note.Rest dotted-half>
         >>> measureRest.duration.type
         'half'
         >>> measureRest.duration.quarterLength
@@ -2399,20 +2401,46 @@ class MeasureParser(XMLParserBase):
 
     def xmlBackup(self, mxObj):
         '''
-        parse a backup tag by changing .offsetMeasureNote
+        Parse a backup tag by changing :attr:`offsetMeasureNote`.
+
+        A floor of 0.0 is enforced in case of float rounding issues.
+
+        >>> MP = musicxml.xmlToM21.MeasureParser()
+        >>> MP.divisions = 100
+        >>> MP.offsetMeasureNote = 1.9979
+
+        >>> from xml.etree.ElementTree import fromstring as EL
+        >>> mxBackup = EL('<backup><duration>100</duration></backup>')
+        >>> MP.xmlBackup(mxBackup)
+        >>> MP.offsetMeasureNote
+        0.9979
+
+        >>> MP.xmlBackup(mxBackup)
+        >>> MP.offsetMeasureNote
+        0.0
         '''
-        try:
-            change = float(mxObj.find('duration').text.strip()) / self.divisions
+        mxDuration = mxObj.find('duration')
+        if mxDuration is not None and textStripValid(mxDuration):
+            change = common.numberTools.opFrac(
+                float(mxDuration.text.strip()) / self.divisions
+            )
             self.offsetMeasureNote -= change
-        except AttributeError:
-            pass
+            # check for negative offsets produced by
+            # musicxml durations with float rounding issues
+            # https://github.com/cuthbertLab/music21/issues/971
+            self.offsetMeasureNote = max(self.offsetMeasureNote, 0.0)
 
     def xmlForward(self, mxObj):
         '''
-        parse a forward tag by changing .offsetMeasureNote
+        Parse a forward tag by changing :attr:`offsetMeasureNote`.
         '''
-        change = float(mxObj.find('duration').text.strip()) / self.divisions
-        self.offsetMeasureNote += change
+        mxDuration = mxObj.find('duration')
+        if mxDuration is not None and textStripValid(mxDuration):
+            change = common.numberTools.opFrac(
+                float(mxDuration.text.strip()) / self.divisions
+            )
+            # Allow overfilled measures for now -- TODO(someday): warn?
+            self.offsetMeasureNote += change
 
     def xmlPrint(self, mxPrint):
         '''
@@ -2589,6 +2617,8 @@ class MeasureParser(XMLParserBase):
         <music21.pitch.Pitch A3>
         >>> c.pitches[1]
         <music21.pitch.Pitch B3>
+        >>> c.duration
+        <music21.duration.Duration unlinked type:quarter quarterLength:0.75>
 
         >>> a = EL('<note><pitch><step>A</step><octave>3</octave></pitch>'
         ...        + qnDuration
@@ -3021,7 +3051,7 @@ class MeasureParser(XMLParserBase):
         >>> mxr = EL('<note><rest/><duration>5040</duration><type>eighth</type></note>')
         >>> r = MP.xmlToRest(mxr)
         >>> r
-        <music21.note.Rest rest>
+        <music21.note.Rest eighth>
         >>> r.duration.quarterLength
         0.5
 
@@ -3030,7 +3060,7 @@ class MeasureParser(XMLParserBase):
         ...              '</rest><duration>5040</duration><type>eighth</type></note>')
         >>> r = MP.xmlToRest(mxr)
         >>> r
-        <music21.note.Rest rest>
+        <music21.note.Rest eighth>
 
         A rest normally lies at B4 in treble clef, but here we have put it at
         G4, so we'll shift it down two steps.
@@ -3208,6 +3238,38 @@ class MeasureParser(XMLParserBase):
         'eighth'
         >>> c.dots
         1
+
+        If the `<duration>` doesn't match the `<type>` and `<dots>`,
+        an unlinked duration is created so that `.quarterLength` agrees with
+        `<duration>` but the notated types can still be represented.
+
+        Create a second dot on `mxNote` and parse again, observing the identical
+        `quarterLength`:
+
+        >>> from xml.etree.ElementTree import SubElement
+        >>> unused = SubElement(mxNote, 'dot')
+        >>> c2 = MP.xmlToDuration(mxNote)
+        >>> c2
+        <music21.duration.Duration unlinked type:eighth quarterLength:0.75>
+        >>> c2.quarterLength
+        0.75
+        >>> c2.type
+        'eighth'
+        >>> c2.dots
+        2
+
+        Grace note durations will be converted later to GraceDurations:
+
+        >>> mxDuration = mxNote.find('duration')
+        >>> mxNote.remove(mxDuration)
+        >>> mxGrace = SubElement(mxNote, 'grace')
+        >>> MP.xmlToDuration(mxNote, inputM21=c2)
+        >>> c2
+        <music21.duration.Duration unlinked type:eighth quarterLength:0.0>
+        >>> gn1 = note.Note(duration=c2)
+        >>> gn2 = MP.xmlGraceToGrace(mxGrace, gn1)
+        >>> gn2.duration
+        <music21.duration.GraceDuration unlinked type:eighth quarterLength:0.0>
         '''
         numDots = 0
         tuplets = ()
@@ -3221,7 +3283,7 @@ class MeasureParser(XMLParserBase):
         mxDuration = mxNote.find('duration')
         if mxDuration is not None:
             noteDivisions = float(mxDuration.text.strip())
-            qLen = noteDivisions / divisions
+            qLen = common.numberTools.opFrac(noteDivisions / divisions)
         else:
             qLen = 0.0
 
@@ -3249,36 +3311,36 @@ class MeasureParser(XMLParserBase):
             # TODO: empty-placement
 
         # two ways to create durations, raw (from qLen) and cooked (from type, time-mod, dots)
-        if forceRaw:
-            if d is not None:
-                # environLocal.printDebug(['forced to use raw duration', durRaw])
-                durRaw = duration.Duration()  # raw just uses qLen
-                # the qLen set here may not be computable, but is not immediately
-                # computed until setting components
-                durRaw.quarterLength = qLen
-                try:
-                    d.components = durRaw.components
-                except duration.DurationException:  # TODO: Test
-                    if qLen:
-                        qLenRounded = 2.0 ** round(math.log2(qLen))
-                    else:
-                        qLenRounded = 0.0
-                    environLocal.printDebug(
-                        ['mxToDuration',
-                         f'rounding duration to {qLenRounded} as type is not'
-                         + 'defined and raw quarterLength '
-                         + f'({qLen}) is not a computable duration'])
-                    # environLocal.printDebug(['mxToDuration', 'raw qLen', qLen, durationType,
-                    #                         'mxNote:',
-                    #                         ET.tostring(mxNote, encoding='unicode'),
-                    #                         'last mxDivisions:', divisions])
-                    durRaw.quarterLength = qLenRounded
-            else:
-                d = duration.Duration(quarterLength=qLen)
-        else:  # a cooked version builds up from pieces
+        if d is not None:
+            durRaw = duration.Duration(quarterLength=qLen)  # raw just uses qLen
+            # the qLen set here may not be computable, but is not immediately
+            # computed until setting components
+            try:
+                d.components = durRaw.components
+            except duration.DurationException:  # TODO: Test
+                if qLen:
+                    qLenRounded = 2.0 ** round(math.log2(qLen))
+                else:
+                    qLenRounded = 0.0
+                environLocal.printDebug(
+                    ['mxToDuration',
+                        f'rounding duration to {qLenRounded} as type is not'
+                        + 'defined and raw quarterLength '
+                        + f'({qLen}) is not a computable duration'])
+                durRaw.quarterLength = qLen = qLenRounded
+        else:
+            d = duration.Duration(quarterLength=qLen)
+        # can't do this unless we have a type, so if not forceRaw
+        if not forceRaw:  # a cooked version builds up from pieces
             dt = duration.durationTupleFromTypeDots(durationType, numDots)
+            if (dt.quarterLength == qLen) and not tuplets:
+                # raw == cooked, so we're done
+                # but this comparison gives false positives if tuplets are involved
+                # don't bother with isclose; merely trying to short-circuit
+                return d if inputM21 is None else None
             if d is not None:
                 d.clear()
+                d.tuplets = []
                 d.addDurationTuple(dt)
             else:
                 d = duration.Duration(durationTuple=dt)
@@ -3286,6 +3348,12 @@ class MeasureParser(XMLParserBase):
             for tup in tuplets:
                 d.appendTuplet(tup)
 
+            # Second check against qLen (raw), now with tuplets
+            # if not almost equal, create unlinked Duration and set raw qLen
+            if not isclose(d.quarterLength, qLen, abs_tol=1e-7):
+                d.linked = False
+                d.quarterLength = qLen
+
         if inputM21 is None:
             return d
 
@@ -6514,13 +6582,13 @@ class Test(unittest.TestCase):
         '''
         test that a note with nested tuplets gets converted properly.
         '''
-        mxN = '''
+        mxN = f'''
         <note default-x="347">
         <pitch>
           <step>D</step>
           <octave>5</octave>
         </pitch>
-        <duration>4</duration>
+        <duration>{defaults.divisionsPerQuarter * 0.5 * (2/3) * (2/3)}</duration>
         <voice>1</voice>
         <type>eighth</type>
         <time-modification>
diff --git a/music21/note.py b/music21/note.py
index edbbc106e..628a2c82b 100644
--- a/music21/note.py
+++ b/music21/note.py
@@ -494,7 +494,7 @@ class GeneralNote(base.Music21Object):
     double-dotted sixteenth note.
 
     In almost every circumstance, you should
-    create note.Note() or note.Rest() or note.Chord()
+    create note.Note() or note.Rest() or chord.Chord()
     objects directly, and not use this underlying
     structure.
 
@@ -839,9 +839,9 @@ class GeneralNote(base.Music21Object):
         >>> ng.duration.isGrace
         True
         >>> ng.duration
-        <music21.duration.GraceDuration unlinked type:zero quarterLength:0.0>
+        <music21.duration.GraceDuration unlinked type:half quarterLength:0.0>
         >>> ng.duration.type
-        'zero'
+        'half'
         >>> ng.duration.components
         (DurationTuple(type='half', dots=0, quarterLength=0.0),)
 
@@ -850,7 +850,7 @@ class GeneralNote(base.Music21Object):
 
         >>> ng2 = n.getGrace(appoggiatura=True)
         >>> ng2.duration
-        <music21.duration.AppoggiaturaDuration unlinked type:zero quarterLength:0.0>
+        <music21.duration.AppoggiaturaDuration unlinked type:half quarterLength:0.0>
         >>> ng2.duration.slash
         False
 
@@ -860,7 +860,7 @@ class GeneralNote(base.Music21Object):
         >>> r = note.Rest(quarterLength=0.5)
         >>> r.getGrace(inPlace=True)
         >>> r.duration
-        <music21.duration.GraceDuration unlinked type:zero quarterLength:0.0>
+        <music21.duration.GraceDuration unlinked type:eighth quarterLength:0.0>
         '''
         if inPlace is False:
             e = copy.deepcopy(self)
@@ -1733,7 +1733,15 @@ class Rest(GeneralNote):
         self.fullMeasure = 'auto'  # see docs; True, False, 'always',
 
     def _reprInternal(self):
-        return self.name
+        duration_name = self.duration.fullName.lower()
+        if len(duration_name) < 15:  # dotted quarter = 14
+            return duration_name.replace(' ', '-')
+        else:
+            ql = self.duration.quarterLength
+            if ql == int(ql):
+                ql = int(ql)
+            ql_string = str(ql)
+            return f'{ql_string}ql'
 
     def __eq__(self, other):
         '''
@@ -1775,25 +1783,6 @@ class Rest(GeneralNote):
         return self.duration.fullName + ' Rest'
 
 
-class SpacerRest(Rest):
-    '''
-    This is exactly the same as a rest, but it is a SpacerRest.
-    This object should only be used for making hidden space in a score in lilypond.
-
-    DEPRECATED in v.7 -- use a normal rest with .hideObjectOnPrint
-
-    >>> sr = note.SpacerRest(type='whole')
-    >>> sr
-    <music21.note.SpacerRest rest duration=4.0>
-    '''
-
-    def __init__(self, *arguments, **keywords):
-        super().__init__(**keywords)
-
-    def _reprInternal(self):
-        return f'{self.name} duration={self.duration.quarterLength}'
-
-
 # ------------------------------------------------------------------------------
 # test methods and classes
 
@@ -2185,7 +2174,7 @@ class Test(unittest.TestCase):
 
 # ------------------------------------------------------------------------------
 # define presented order in documentation
-_DOC_ORDER = [Note, Rest, SpacerRest, Unpitched, NotRest, GeneralNote, Lyric]
+_DOC_ORDER = [Note, Rest, Unpitched, NotRest, GeneralNote, Lyric]
 
 if __name__ == '__main__':
     # sys.arg test options will be used in mainTest()
diff --git a/music21/noteworthy/translate.py b/music21/noteworthy/translate.py
index b71ca160c..fe0fd0c1b 100644
--- a/music21/noteworthy/translate.py
+++ b/music21/noteworthy/translate.py
@@ -519,8 +519,8 @@ class NoteworthyTranslator:
         >>> nwt.translateRest({'Dur': '4th'})
         >>> measureIn.show('text')
         {0.0} <music21.note.Note C#>
-        {2.0} <music21.note.Rest rest>
-        {2.75} <music21.note.Rest rest>
+        {2.0} <music21.note.Rest dotted-eighth>
+        {2.75} <music21.note.Rest quarter>
 
         '''
         durationInfo = attributes['Dur']
@@ -628,7 +628,7 @@ class NoteworthyTranslator:
         >>> nwt.currentKey.sharps
         4
         >>> measureIn.show('text')
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest dotted-half>
         {3.0} <music21.key.KeySignature of 4 sharps>
         '''
         ke = attributes['Signature']
diff --git a/music21/romanText/rtObjects.py b/music21/romanText/rtObjects.py
index 27e3cdec2..bc565e38e 100644
--- a/music21/romanText/rtObjects.py
+++ b/music21/romanText/rtObjects.py
@@ -1394,6 +1394,7 @@ class RTFile(prebase.ProtoM21Object):
         '''
         for encoding in ('utf-8', 'macintosh', 'latin-1', 'utf-16'):
             try:
+                # pylint: disable=consider-using-with
                 self.file = io.open(filename, encoding=encoding)
                 if self.file is not None:
                     break
@@ -1402,6 +1403,7 @@ class RTFile(prebase.ProtoM21Object):
         if self.file is None:
             for encoding in ('utf-8', 'macintosh', 'latin-1', 'utf-16', None):
                 try:
+                    # pylint: disable=consider-using-with
                     self.file = io.open(filename, encoding=encoding, errors='ignore')
                     if self.file is not None:
                         break
diff --git a/music21/scale/scala/__init__.py b/music21/scale/scala/__init__.py
index e68a2e1c9..3d7e49c8b 100644
--- a/music21/scale/scala/__init__.py
+++ b/music21/scale/scala/__init__.py
@@ -404,7 +404,7 @@ class ScalaFile:
         '''
         Open a file for reading
         '''
-        self.file = io.open(fp, mode, encoding='latin-1')
+        self.file = io.open(fp, mode, encoding='latin-1')  # pylint: disable=consider-using-with
         self.fileName = os.path.basename(fp)
 
     def openFileLike(self, fileLike):
diff --git a/music21/search/base.py b/music21/search/base.py
index 662b1676e..8310295de 100644
--- a/music21/search/base.py
+++ b/music21/search/base.py
@@ -343,7 +343,7 @@ def rhythmicSearch(thisStreamOrIterator, searchList):
         {2.0} <music21.note.Note F>
     {6.0} <music21.stream.Measure 3 offset=6.0>
         {0.5} <music21.note.Note C>
-        {2.0} <music21.note.Rest rest>
+        {2.0} <music21.note.Rest quarter>
         {3.0} <music21.bar.Barline type=final>
 
     Now we will search for all dotted-quarter/eighth elements in the Stream:
diff --git a/music21/stream/base.py b/music21/stream/base.py
index 1ea8d8a05..c121c48cb 100644
--- a/music21/stream/base.py
+++ b/music21/stream/base.py
@@ -115,11 +115,16 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
     The duration, however, can be "unlinked" and explicitly
     set independent of the Stream's contents.
 
-    The first element passed to the Stream is an optional list,
-    tuple, or other Stream of music21 objects which is used to
-    populate the Stream by inserting each object at
+    The first element passed to the Stream is an optional single
+    Music21Object or a list, tuple, or other Stream of Music21Objects
+    which is used to populate the Stream by inserting each object at
     its :attr:`~music21.base.Music21Object.offset`
-    property. Other arguments and keywords are ignored, but are
+    property. One special case is when every such object, such as a newly created
+    one, has no offset. Then, so long as the entire list is not composed of
+    non-Measure Stream subclasses like Parts or Voices, each element is appended,
+    creating a sequence of elements in time, rather than synchrony.
+
+    Other arguments and keywords are ignored, but are
     allowed so that subclassing the Stream is easier.
 
     >>> s1 = stream.Stream()
@@ -152,8 +157,51 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
     >>> s2.show('text')
     {0.0} <music21.clef.TrebleClef>
     {0.0} <music21.stream.Part embeddedPart>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest quarter>
     {1.0} <music21.note.Note E->
+
+    New in v7 -- providing a single element now works:
+
+    >>> s = stream.Stream(meter.TimeSignature())
+    >>> s.first()
+    <music21.meter.TimeSignature 4/4>
+
+    New in v7 -- providing a list of objects or Measures (but not other Stream
+    subclasses such as Parts) now positions sequentially, i.e. appends:
+
+    >>> s2 = stream.Measure([note.Note(), note.Note(), bar.Barline()])
+    >>> s2.show('text')
+    {0.0} <music21.note.Note C>
+    {1.0} <music21.note.Note C>
+    {2.0} <music21.bar.Barline type=regular>
+
+    A list of measures will let each be appended:
+
+    >>> m1 = stream.Measure(n1, number=1)
+    >>> m2 = stream.Measure(note.Rest(), number=2)
+    >>> s3 = stream.Part([m1, m2])
+    >>> s3.show('text')
+    {0.0} <music21.stream.Measure 1 offset=0.0>
+        {1.0} <music21.note.Note E->
+    {1.5} <music21.stream.Measure 2 offset=1.5>
+        {0.0} <music21.note.Rest quarter>
+
+    Here, every element is a Stream that's not a Measure, so we instead insert:
+
+    >>> s4 = stream.Score([stream.PartStaff(n1), stream.PartStaff(note.Rest())])
+    >>> s4.show('text')
+    {0.0} <music21.stream.PartStaff 0x...>
+        {1.0} <music21.note.Note E->
+    {0.0} <music21.stream.PartStaff 0x...>
+        {0.0} <music21.note.Rest quarter>
+
+    Create nested streams in one fell swoop:
+
+    >>> s5 = stream.Score(stream.Part(stream.Measure(note.Note())))
+    >>> s5.show('text')
+    {0.0} <music21.stream.Part 0x...>
+        {0.0} <music21.stream.Measure 0 offset=0.0>
+            {0.0} <music21.note.Note C>
     '''
     # this static attributes offer a performance boost over other
     # forms of checking class
@@ -227,14 +275,31 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         # experimental
         self._mutable = True
 
-        if givenElements and common.isIterable(givenElements):
-            # TODO: perhaps convert a single element into a list?
-            for e in givenElements:
-                try:
-                    self.coreInsert(e.offset, e)
-                except (AttributeError, TypeError):
-                    raise StreamException(f'Unable to insert {e}')
-            self.coreElementsChanged()
+        if givenElements is None:
+            return
+
+        if isinstance(givenElements, Stream) or not common.isIterable(givenElements):
+            givenElements = [givenElements]
+
+        # Append rather than insert if every offset is 0.0
+        # but not if every element is a stream subclass other than a Measure
+        # (i.e. Opus, Score, Part, or Voice)
+        append: bool = False
+        try:
+            append = all(e.offset == 0.0 for e in givenElements)
+        except AttributeError:
+            pass  # appropriate failure will be raised by coreGuardBeforeAddElement()
+        if append and all((e.isStream and not e.isMeasure) for e in givenElements):
+            append = False
+
+        for e in givenElements:
+            self.coreGuardBeforeAddElement(e)
+            if append:
+                self.coreAppend(e)
+            else:
+                self.coreInsert(e.offset, e)
+
+        self.coreElementsChanged()
 
     def _reprInternal(self):
         if self.id is not None:
@@ -316,95 +381,137 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         '''
         Get a Music21Object from the Stream using a variety of keys or indices.
 
-        If an int is given, the Music21Object at the index is returned. If the Stream is sorted
-        (if isSorted is True), the elements are returned in order.
+        If an int is given, the Music21Object at the index is returned, as if it were a list
+        or tuple:
 
-        If a class name is given (as a string or name),
-        :meth:`~music21.stream.Stream.getElementsByClass` is used to return a Stream of the
-        elements that match the requested class.
-
-        If a string is given, :meth:`~music21.stream.Stream.getElementById` first, then
-        (if no results are found) :meth:`~music21.stream.Stream.getElementsByGroup` is used to
-        collect and return elements as a Stream.
+        >>> c = note.Note('C')
+        >>> d = note.Note('D')
+        >>> e = note.Note('E')
+        >>> r1 = note.Rest()
+        >>> f = note.Note('F')
+        >>> g = note.Note('G')
+        >>> r2 = note.Rest()
+        >>> a = note.Note('A')
+        >>> s = stream.Stream([c, d, e, r1, f, g, r2, a])
 
-        >>> a = stream.Part(id='hello')
-        >>> names = ['C', 'D', 'E', 'F', 'G', 'A']
-        >>> for i in range(6):
-        ...     name = names[i]
-        ...     a.insert(i + 1, note.Note(name))
-        >>> a[0]
+        >>> s[0]
         <music21.note.Note C>
-        >>> a[-1]
+        >>> s[-1]
         <music21.note.Note A>
 
         Out of range notes raise an IndexError:
 
-        >>> a[99]
+        >>> s[99]
         Traceback (most recent call last):
-        IndexError: attempting to access index 99 while elements is of size 6
-        >>> subslice = a[2:5]
+        IndexError: attempting to access index 99 while elements is of size 8
+
+        If a slice of indices is given, a list of elements is returned, as if the Stream
+        were a list or Tuple.
+
+        >>> subslice = s[2:5]
         >>> subslice
-        <music21.stream.Part hello>
-        >>> subslice.derivation
-        <Derivation of <music21.stream.Part hello> from
-            <music21.stream.Part hello> via '__getitem__'>
+        [<music21.note.Note E>, <music21.note.Rest quarter>, <music21.note.Note F>]
         >>> len(subslice)
         3
-        >>> a[1].offset
-        2.0
+        >>> s[1].offset
+        1.0
         >>> subslice[1].offset
-        4.0
+        3.0
 
-        >>> b = note.Note()
-        >>> b.id = 'green'
-        >>> b.groups.append('violin')
-        >>> a.insert(b)
 
-        Get an item by class
+        If a class is given then an iterator of elements
+        that match the requested class(es) is returned, similar
+        to `Stream().recurse().getElementsByClass()`.
 
-        >>> allNotes = a[note.Note]
-        >>> allNotes
-        <music21.stream.Part hello>
-        >>> allNotes.derivation
-        <Derivation of <music21.stream.Part hello> from
-            <music21.stream.Part hello> via 'getElementsByClass'>
-        >>> allNotes[0] is b
-        True
+        >>> len(s)
+        8
+        >>> len(s[note.Rest])
+        2
+        >>> len(s[note.Note])
+        6
 
-        Get items by groups:
+        >>> for n in s[note.Note]:
+        ...     print(n.name, end=' ')
+        C D E F G A
+
+        Note that this iterator is recursive by default.
+
+        >>> c_sharp = note.Note('C#')
+        >>> v = stream.Voice([c_sharp])
+        >>> s.insert(0.5, c_sharp)
+
+        >>> len(s[note.Note])
+        7
 
-        >>> violinGroup = a['violin']
-        >>> violinGroup
-        <music21.stream.Part hello>
-        >>> violinGroup.derivation
-        <Derivation of <music21.stream.Part hello> from
-            <music21.stream.Part hello> via 'getElementsByGroup'>
-        >>> violinGroup[0] is b
-        True
 
-        Get a single element by id:
+        The actual object returned by `s[module.Class]` is a
+        :class:`~music21.stream.iterator.RecursiveIterator` and has all the functions
+        available on it:
 
-        >>> a['green'] is b
+        >>> s[note.Note]
+        <...>
+
+        If no elements of the class are found, no error is raised in version 7:
+
+        >>> list(s[layout.StaffLayout])
+        []
+
+
+        If the key is a string, it is treated as a `querySelector` as defined in
+        :meth:`~music21.stream.iterator.getElementsByQuerySelector`, namely that bare strings
+        are treated as class names, strings beginning with `#` are id-queries, and strings
+        beginning with `.` are group queries.
+
+        We can set some ids and groups for demonstrating.
+
+        >>> a.id = 'last_a'
+        >>> c.groups.append('ghost')
+        >>> e.groups.append('ghost')
+
+        'Note' is treated as a class name and returns a `RecursiveIterator`:
+
+        >>> for n in s['Note']:
+        ...     print(n.name, end=' ')
+        C C# D E F G A
+
+        '.ghost', because it begins with `.`, is treated as a class name and
+        returns a `RecursiveIterator`:
+
+
+        >>> for n in s['.ghost']:
+        ...     print(n.name, end=' ')
+        C E
+
+        A query selector with a `#`:
+
+        >>> s['#last_a']
+        <music21.note.Note A>
+
+        >>> s['#nothing'] is None
         True
 
-        If a string or class is not found, a KeyError will be raised:
 
-        >>> a['purple']
-        Traceback (most recent call last):
-        KeyError: 'provided key (purple) does not match any id or group'
+        Any other query raises a TypeError:
 
-        >>> a[layout.StaffLayout]
+        >>> s[0.5]
         Traceback (most recent call last):
-        KeyError: "provided class (<class 'music21.layout.StaffLayout'>) does
-            not match any contained Objects"
+        TypeError: Streams can get items by int, slice, class, or string query; got <class 'float'>
+
 
-        Changed in v7. -- out of range indexes now raise an IndexError, not StreamException
+        Changed in v7:
+
+          - out of range indexes now raise an IndexError, not StreamException
+          - strings ('Note', '#id', '.group') are now treated like a query selector.
+          - slices with negative indices now supported
+          - Unsupported types now raise TypeError
+          - Class and Group searches now return a recursive `StreamIterator` rather than a `Stream`
+          - Slice searches now return a list of elements rather than a `Stream`
         '''
         # need to sort if not sorted, as this call may rely on index positions
         if not self.isSorted and self.autoSort:
             self.sort()  # will set isSorted to True
 
-        if common.isNum(k):
+        if isinstance(k, int):
             match = None
             # handle easy and most common case first
             if 0 <= k < len(self._elements):
@@ -426,36 +533,27 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         elif isinstance(k, slice):  # get a slice of index values
             # manually inserting elements is critical to setting the element
             # locations
-            try:
-                found = self.cloneEmpty(derivationMethod='__getitem__')
-            except TypeError:  # pragma: no cover
-                raise StreamException(f'Error in defining class: {self.__class__}'
-                                      + 'Stream subclasses and Music21Objects cannot have required '
-                                      + 'arguments in __init__')
-            for e in self.elements[k]:
-                found.coreInsert(self.elementOffset(e), e)
+            searchElements = self._elements
+            if (k.start is not None and k.start < 0) or (k.stop is not None and k.stop < 0):
+                # Must use .elements property to incorporate end elements
+                searchElements = self.elements
 
-            found.coreElementsChanged(clearIsSorted=False)
-            return found
+            return searchElements[k]
+
+        elif isinstance(k, type):
+            return self.recurse().getElementsByClass(k)
 
         elif isinstance(k, str):
-            # first search id, then search groups
-            idMatch = self.getElementById(k)
-            if idMatch is not None:
-                return idMatch
-            else:  # search groups, return first element match
-                groupStream = self.iter.getElementsByGroup(k)
-                if groupStream:
-                    return groupStream.stream()
-                else:
-                    raise KeyError(f'provided key ({k}) does not match any id or group')
-        elif isinstance(k, type(type)):
-            # assume it is a class name
-            classStream = self.iter.getElementsByClass(k)
-            if classStream:
-                return classStream.stream()
+            querySelectorIterator = self.recurse().getElementsByQuerySelector(k)
+            if '#' in k:
+                # an id anywhere in the query selector should return only one element
+                return querySelectorIterator.first()
             else:
-                raise KeyError(f'provided class ({k}) does not match any contained Objects')
+                return querySelectorIterator
+
+        raise TypeError(
+            f'Streams can get items by int, slice, class, or string query; got {type(k)}'
+        )
 
     def first(self):
         '''
@@ -1972,12 +2070,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         except (ValueError, TypeError):
             raise StreamException(f'Offset {offset!r} must be a number.')
 
-        if not isinstance(item, base.Music21Object):
-            raise StreamException('to put a non Music21Object in a stream, '
-                                  + 'create a music21.ElementWrapper for the item')
         element = item
 
-        # checks if element is self; possibly performs additional checks
+        # checks if element is self, among other checks
         self.coreGuardBeforeAddElement(element)
         # main insert procedure here
 
@@ -2011,7 +2106,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> s.append(c1)
         >>> s.show('text')
         {0.0} <music21.note.Note D>
-        {2.0} <music21.note.Rest rest>
+        {2.0} <music21.note.Rest half>
         {4.0} <music21.chord.Chord C4 E4>
 
         Save the original Streams for later
@@ -2056,7 +2151,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         Chords can also be inserted into rests:
 
         >>> s3.getElementsByOffset(2.0).first()
-        <music21.note.Rest rest>
+        <music21.note.Rest half>
         >>> s3.insertIntoNoteOrChord(2.0, chord.Chord('C4 E4 G#4'))
         >>> s3.show('text')
         {0.0} <music21.note.Note D>
@@ -2069,7 +2164,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
 
         >>> s4.show('text', addEndTimes=True)
         {0.0 - 2.0} <music21.note.Note D>
-        {2.0 - 4.0} <music21.note.Rest rest>
+        {2.0 - 4.0} <music21.note.Rest half>
         {4.0 - 5.0} <music21.chord.Chord C4 E4>
 
         >>> for i in [0.0, 4.0, 6.0]:  # skipping 2.0 for now
@@ -2079,9 +2174,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> s4.insertIntoNoteOrChord(2.0, r)
         >>> s4.show('text', addEndTimes=True)
         {0.0 - 2.0} <music21.note.Note D>
-        {2.0 - 4.0} <music21.note.Rest rest>
+        {2.0 - 4.0} <music21.note.Rest half>
         {4.0 - 5.0} <music21.chord.Chord C4 E4>
-        {6.0 - 7.0} <music21.note.Rest rest>
+        {6.0 - 7.0} <music21.note.Rest quarter>
 
         Notice that (1) the original duration and not the new duration is used, unless
         there is no element at that place, and (2) if an element is put into a place where
@@ -2349,20 +2444,15 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         else:
             item = itemOrList
 
-        try:
-            unused = item.isStream  # will raise attribute error
-            element = item
-        except AttributeError:  # need to wrap
-            # element = music21.ElementWrapper(item)
-            raise StreamException('to put a non Music21Object in a stream, '
-                                  + 'create a music21.ElementWrapper for the item')
+        element = item
+        # checks if element is self, among other checks
+        self.coreGuardBeforeAddElement(element)
+
         # cannot support elements with Durations in the highest time list
         if element.duration.quarterLength != 0:
             raise StreamException('cannot insert an object with a non-zero '
                                   + 'Duration into the highest time elements list')
 
-        # checks of element is self; possibly performs additional checks
-        self.coreGuardBeforeAddElement(element)
         self.coreStoreAtEnd(element)
         # Streams cannot reside in end elements, thus do not update is flat
         self.coreElementsChanged(updateIsFlat=False)
@@ -3823,7 +3913,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
 
         >>> t5 = st1.getElementAfterElement(n1, [note.Rest])
         >>> t5
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
         >>> t5 is r3
         True
         >>> t6 = st1.getElementAfterElement(n1, [note.Rest, note.Note])
@@ -4274,14 +4364,14 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
             {0.0} <music21.clef.TrebleClef>
             {0.0} <music21.key.Key of f# minor>
             {0.0} <music21.meter.TimeSignature 4/4>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest quarter>
         {1.0} <music21.stream.Measure 1 offset=1.0>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest whole>
         {5.0} <music21.stream.Measure 2 offset=5.0>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest whole>
         {9.0} <music21.stream.Measure 3 offset=9.0>
             {0.0} <music21.layout.SystemLayout>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest whole>
         {13.0} <music21.stream.Measure 4 offset=13.0>
         ...
 
@@ -4350,12 +4440,12 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.clef.TrebleClef>
                 {0.0} <music21.key.Key of f# minor>
                 {0.0} <music21.meter.TimeSignature 4/4>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest quarter>
             {1.0} <music21.stream.Measure 1 offset=1.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest whole>
                 ...
             {33.0} <music21.stream.Measure 9 offset=33.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest dotted-half>
                 {3.0} <music21.bar.Barline type=final>
         {0.0} <music21.stream.Part Alto>
             {0.0} <music21.instrument.Instrument 'P2: Alto: Instrument 2'>
@@ -4363,12 +4453,12 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.clef.TrebleClef>
                 {0.0} <music21.key.Key of f# minor>
                 {0.0} <music21.meter.TimeSignature 4/4>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest quarter>
             {1.0} <music21.stream.Measure 1 offset=1.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest whole>
             ...
             {33.0} <music21.stream.Measure 9 offset=33.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest dotted-half>
                 {3.0} <music21.bar.Barline type=final>
         {0.0} <music21.layout.StaffGroup ...>
 
@@ -4391,9 +4481,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> pt = p.template(retainVoices=False)
         >>> pt.show('text')
         {0.0} <music21.stream.Measure 1 offset=0.0>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest whole>
         {4.0} <music21.stream.Measure 2 offset=4.0>
-            {0.0} <music21.note.Rest rest>
+            {0.0} <music21.note.Rest whole>
         >>> pt[0][0].quarterLength
         4.0
 
@@ -5903,10 +5993,10 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> s.show('text', addEndTimes=True)
         {0.0 - 6.3} <music21.stream.Part part1>
             {4.0 - 5.0} <music21.note.Note C#>
-            {5.3 - 6.3} <music21.note.Rest rest>
+            {5.3 - 6.3} <music21.note.Rest quarter>
         {0.0 - 6.5} <music21.stream.Part part2>
             {2.12 - 4.12} <music21.note.Note D->
-            {5.5 - 6.5} <music21.note.Rest rest>
+            {5.5 - 6.5} <music21.note.Rest quarter>
 
         >>> cc = s.chordify()
 
@@ -5916,11 +6006,11 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         Fraction(22, 25)
 
         >>> cc.show('text', addEndTimes=True)
-        {0.0 - 2.12} <music21.note.Rest rest>
+        {0.0 - 2.12} <music21.note.Rest 53/25ql>
         {2.12 - 4.0} <music21.chord.Chord D-4>
         {4.0 - 4.12} <music21.chord.Chord C#4 D-4>
         {4.12 - 5.0} <music21.chord.Chord C#4>
-        {5.0 - 6.5} <music21.note.Rest rest>
+        {5.0 - 6.5} <music21.note.Rest dotted-quarter>
 
         Here's how addPartIdAsGroup works:
 
@@ -8829,8 +8919,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                         e.editorial.offsetQuantizationError = signedError * sign
                 if processDurations:
                     ql = e.duration.quarterLength
-                    if ql < 0:  # buggy MIDI file?
-                        ql = 0
+                    ql = max(ql, 0)  # negative ql possible in buggy MIDI files?
                     unused_error, qlNew, signedError = bestMatch(
                         float(ql), quarterLengthDivisors)
                     # Enforce nonzero duration for non-grace notes
@@ -9366,7 +9455,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         :class:`~music21.note.Note`,
         :class:`~music21.chord.Chord`,
         :class:`~music21.note.Rest`) but also their subclasses, such as
-        `Harmony` objects (`ChordSymbols`, `FiguredBass`), `SpacerRests` etc.
+        `Harmony` objects (`ChordSymbols`, `FiguredBass`), etc.
 
 
         >>> s1 = stream.Stream()
@@ -9378,7 +9467,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> s1.show('text')
         {0.0} <music21.key.KeySignature of no sharps or flats>
         {0.0} <music21.note.Note B>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest quarter>
         {2.0} <music21.chord.Chord A B->
 
         `.notesAndRests` removes the `KeySignature` object but keeps the `Rest`.
@@ -9386,7 +9475,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> notes1 = s1.notesAndRests.stream()
         >>> notes1.show('text')
         {0.0} <music21.note.Note B>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest quarter>
         {2.0} <music21.chord.Chord A B->
 
         The same caveats about `Stream` classes and `.flat` in `.notes` apply here.
@@ -9422,7 +9511,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> p1.show('text')
         {0.0} <music21.key.KeySignature of no sharps or flats>
         {0.0} <music21.note.Note B>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest quarter>
         {2.0} <music21.chord.Chord A B->
 
         >>> noteStream = p1.notes.stream()
@@ -9959,8 +10048,8 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         >>> s.storeAtEnd(bar.Barline('final'))
         >>> gapStream = s.findGaps()
         >>> gapStream.show('text', addEndTimes=True)
-        {0.0 - 1.0} <music21.note.Rest rest>
-        {3.0 - 5.0} <music21.note.Rest rest>
+        {0.0 - 1.0} <music21.note.Rest quarter>
+        {3.0 - 5.0} <music21.note.Rest half>
 
         Returns None if not gaps:
 
@@ -10611,7 +10700,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.stream.Voice 3>
                     {0.0} <music21.note.Note E>
                     ...
-                    {3.0} <music21.note.Rest rest>
+                    {3.0} <music21.note.Rest quarter>
                 {0.0} <music21.stream.Voice 4>
                     {0.0} <music21.note.Note F#>
                     ...
@@ -10620,13 +10709,13 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.stream.Voice 3>
                     {0.0} <music21.note.Note E>
                     ...
-                    {3.0} <music21.note.Rest rest>
+                    {3.0} <music21.note.Rest quarter>
                 {0.0} <music21.stream.Voice 4>
                     {0.0} <music21.note.Note E>
                     ...
                     {3.5} <music21.note.Note A>
             {8.0} <music21.stream.Measure 3 offset=8.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest whole>
                 {4.0} <music21.bar.Barline type=final>
         {0.0} <music21.layout.ScoreLayout>
 
@@ -10640,13 +10729,13 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                 {0.0} <music21.meter.TimeSignature 4/4>
                 {0.0} <music21.note.Note E>
                 ...
-                {3.0} <music21.note.Rest rest>
+                {3.0} <music21.note.Rest quarter>
             {4.0} <music21.stream.Measure 2 offset=4.0>
                 {0.0} <music21.note.Note E>
                 ...
-                {3.0} <music21.note.Rest rest>
+                {3.0} <music21.note.Rest quarter>
             {8.0} <music21.stream.Measure 3 offset=8.0>
-                {0.0} <music21.note.Rest rest>
+                {0.0} <music21.note.Rest whole>
                 {4.0} <music21.bar.Barline type=final>
         {0.0} <music21.stream.Part Piano-v1>
             {0.0} <music21.instrument.Instrument 'P1: Piano: '>
@@ -10850,7 +10939,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
 
         Changed in v. 5 -- inPlace is default False and a keyword only arg.
 
-        >>> s = stream.Stream([note.Note(), note.Note(), note.Note()])  # simultaneous
+        >>> s = stream.Stream(note.Note())
+        >>> s.insert(0, note.Note())
+        >>> s.insert(0, note.Note())
         >>> s.makeVoices(inPlace=True)
         >>> len(s.voices)
         3
@@ -10946,9 +11037,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         Note that streams which do not contain any instance of a lyric number will not
         appear anywhere in the final list (not as a [] or otherwise).
 
-        >>> p = stream.Part(s)
-        >>> scr = stream.Score()
-        >>> scr.append(p)
+        >>> scr = stream.Score(s)
 
         >>> scr.lyrics(ignoreBarlines=False, recurse=True)[1]
         [[[<music21.note.Lyric number=1 syllabic=single text='this'>, <...'is'>, <...'a'>, None],
@@ -12206,7 +12295,7 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
                     continue  # do nothing
                 else:
                     shiftOffset = self.elementOffset(cV) - startOffset
-                    r = note.SpacerRest()
+                    r = note.Rest()
                     r.duration.quarterLength = shiftOffset
                     r.style.hideObjectOnPrint = True
                     for el in cV._stream:
@@ -12623,10 +12712,10 @@ class Measure(Stream):
         >>> m.append(note.Note('D4', type='eighth'))
         >>> m.show('text')
         {0.0} <music21.meter.TimeSignature 3/4>
-        {0.0} <music21.note.Rest rest>
-        {0.5} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest eighth>
+        {0.5} <music21.note.Rest eighth>
         {1.0} <music21.note.Note C>
-        {2.0} <music21.note.Rest rest>
+        {2.0} <music21.note.Rest eighth>
         {2.5} <music21.note.Note D>
         >>> m.padAsAnacrusis(useInitialRests=True)
         >>> m.paddingLeft
@@ -12634,7 +12723,7 @@ class Measure(Stream):
         >>> m.show('text')
         {0.0} <music21.meter.TimeSignature 3/4>
         {0.0} <music21.note.Note C>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest eighth>
         {1.5} <music21.note.Note D>
         '''
         if useInitialRests:
@@ -13156,27 +13245,9 @@ class Score(Stream):
         >>> s = corpus.parse('bach/bwv66.6')
         >>> s.parts
         <music21.stream.iterator.StreamIterator for Score:0x104af3a58 @:0>
-        >>> partStream = s.parts.stream()
-        >>> partStream.classes
-        ('Score', 'Stream', 'StreamCoreMixin', 'Music21Object', 'ProtoM21Object', 'object')
-        >>> len(partStream)
+        >>> len(s.parts)
         4
 
-        The partStream object is a full `stream.Score` object, thus the elements inside it
-        can be accessed by index number or by id string, or iterated over:
-
-        >>> partStream[0]
-        <music21.stream.Part Soprano>
-        >>> partStream['Alto']
-        <music21.stream.Part Alto>
-        >>> for p in partStream:
-        ...     print(p.id)
-        Soprano
-        Alto
-        Tenor
-        Bass
-
-
         OMIT_FROM_DOCS
 
         Ensure that getting from cache still will reset the iteration.
diff --git a/music21/stream/core.py b/music21/stream/core.py
index 2a9ea40b0..62e974091 100644
--- a/music21/stream/core.py
+++ b/music21/stream/core.py
@@ -32,6 +32,7 @@ from music21.common.numberTools import opFrac
 from music21 import spanner
 from music21 import tree
 from music21.exceptions21 import StreamException, ImmutableStreamException
+from music21.stream.iterator import StreamIterator
 
 # pylint: disable=attribute-defined-outside-init
 class StreamCoreMixin:
@@ -381,10 +382,15 @@ class StreamCoreMixin:
     # --------------------------------------------------------------------------
     def coreGuardBeforeAddElement(self, element, *, checkRedundancy=True):
         '''
-        Before adding an element, this method provides
-        important checks to that element.
+        Before adding an element, this method performs
+        important checks on that element.
 
-        Used by both insert() and append()
+        Used by:
+
+          - :meth:`~music21.stream.Stream.insert`
+          - :meth:`~music21.stream.Stream.append`
+          - :meth:`~music21.stream.Stream.storeAtEnd`
+          - `Stream.__init__()`
 
         Returns None or raises a StreamException
 
@@ -392,11 +398,27 @@ class StreamCoreMixin:
         >>> s.coreGuardBeforeAddElement(s)
         Traceback (most recent call last):
         music21.exceptions21.StreamException: this Stream cannot be contained within itself
+
+        >>> s.append(s.iter)
+        Traceback (most recent call last):
+        music21.exceptions21.StreamException: cannot insert StreamIterator into a Stream
+        Iterate over it instead (User's Guide chs. 6 and 26)
+
+        >>> s.insert(4, 3.14159)
+        Traceback (most recent call last):
+        music21.exceptions21.StreamException: to put a non Music21Object in a stream,
+        create a music21.ElementWrapper for the item
         '''
-        # using id() here b/c we do not want to get __eq__ comparisons
         if element is self:  # cannot add this Stream into itself
             raise StreamException('this Stream cannot be contained within itself')
+        if not isinstance(element, Music21Object):
+            if isinstance(element, StreamIterator):
+                raise StreamException('cannot insert StreamIterator into a Stream\n'
+                    "Iterate over it instead (User's Guide chs. 6 and 26)")
+            raise StreamException('to put a non Music21Object in a stream, '
+                                  'create a music21.ElementWrapper for the item')
         if checkRedundancy:
+            # using id() here b/c we do not want to get __eq__ comparisons
             idElement = id(element)
             if idElement in self._offsetDict:
                 # now go slow for safety -- maybe something is amiss in the index.
diff --git a/music21/stream/filters.py b/music21/stream/filters.py
index c1d47c0b4..338bf83b5 100644
--- a/music21/stream/filters.py
+++ b/music21/stream/filters.py
@@ -112,7 +112,7 @@ class IsFilter(StreamFilter):
     >>> for el in s.iter.addFilter(isFilter2):
     ...     print(el)
     <music21.note.Note C#>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
 
     '''
     derivationStr = 'is'
@@ -151,14 +151,14 @@ class IsNotFilter(IsFilter):
     >>> for el in s.iter.addFilter(stream.filters.IsNotFilter(n)):
     ...     el
     <music21.key.KeySignature of 3 flats>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
 
     test that resetting works...
 
     >>> for el in s.iter.addFilter(stream.filters.IsNotFilter(n)):
     ...     el
     <music21.key.KeySignature of 3 flats>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
 
 
     multiple...
@@ -229,7 +229,7 @@ class ClassFilter(StreamFilter):
     >>> for x in sI:
     ...     print(x)
     <music21.note.Note C>
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
     <music21.note.Note D>
 
     >>> sI.filters.append(stream.filters.ClassFilter('Note'))
@@ -285,7 +285,7 @@ class ClassNotFilter(ClassFilter):
 
     >>> for x in sI:
     ...     print(x)
-    <music21.note.Rest rest>
+    <music21.note.Rest quarter>
     '''
     derivationStr = 'getElementsNotOfClass'
 
diff --git a/music21/stream/iterator.py b/music21/stream/iterator.py
index 9df634b46..f57177b7e 100644
--- a/music21/stream/iterator.py
+++ b/music21/stream/iterator.py
@@ -89,6 +89,16 @@ class StreamIterator(prebase.ProtoM21Object):
     * For `activeInformation` see above.
 
     Changed in v.5.2 -- all arguments except srcStream are keyword only.
+
+    OMIT_FROM_DOCS
+
+    Informative exception for user error:
+
+    >>> s = stream.Stream()
+    >>> sIter = stream.iterator.StreamIterator(s, filterList=[note.Note])
+    Traceback (most recent call last):
+    TypeError: filterList expects Filters or callables,
+    not types themselves; got <class 'music21.note.Note'>
     '''
     def __init__(self,
                  srcStream: 'music21.stream.Stream',
@@ -122,6 +132,10 @@ class StreamIterator(prebase.ProtoM21Object):
             filterList = [filterList]
         elif isinstance(filterList, (set, tuple)):
             filterList = list(filterList)  # mutable....
+        for x in filterList:
+            if isinstance(x, type):
+                raise TypeError(
+                    f'filterList expects Filters or callables, not types themselves; got {x}')
         # self.filters is a list of expressions that
         # return True or False for an element for
         # whether it should be yielded.
@@ -448,7 +462,7 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> s.recurse().notes.first()
         <music21.note.Note D>
         >>> s.recurse().getElementsByClass('Rest').first()
-        <music21.note.Rest rest>
+        <music21.note.Rest half>
 
         If no elements match, returns None:
 
@@ -499,7 +513,7 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> s.recurse().notes.last()
         <music21.note.Note G>
         >>> s.recurse().getElementsByClass('Rest').last()
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
 
         New in v7.
 
@@ -727,7 +741,7 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> s3 = s.iter.stream()
         >>> s3.show('t')
         {0.0} <music21.note.Note C>
-        {1.0} <music21.note.Rest rest>
+        {1.0} <music21.note.Rest quarter>
         {2.0} <music21.note.Note D>
         {3.0} <music21.bar.Barline type=regular>
 
@@ -897,7 +911,7 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> s.append(note.Note('D'))
         >>> for el in s.iter.getElementsByClass('Rest'):
         ...     print(el)
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
 
 
         ActiveSite is restored...
@@ -916,11 +930,58 @@ class StreamIterator(prebase.ProtoM21Object):
 
         >>> for el in s.iter.getElementsByClass(note.Rest):
         ...     print(el)
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
 
         '''
         return self.addFilter(filters.ClassFilter(classFilterList), returnClone=returnClone)
 
+    def getElementsByQuerySelector(self, querySelector: str, *, returnClone=True):
+        '''
+        First implementation of a query selector, similar to CSS QuerySelectors used in
+        HTML DOM:
+
+        * A leading `#` indicates the id of an element, so '#hello' will find elements
+          with `el.id=='hello'` (should only be one)
+        * A leading `.` indicates the group of an element, so '.high' will find elements
+          with `'high' in el.groups`
+        * Any other string is considered to be the type/class of the element.  So `Note`
+          will find all Note elements.  Can be fully qualified like `note.Note`
+
+        Eventually, more complex query selectors will be implemented.  This is just a start.
+
+        Setting up an example:
+
+        >>> s = converter.parse('tinyNotation: 4/4 GG4 AA4 BB4 r4 C4 D4 E4 F4 r1')
+        >>> s[note.Note].last().id = 'last'
+        >>> for n in s[note.Note]:
+        ...     if n.octave == 3:
+        ...         n.groups.append('tenor')
+
+        >>> list(s.recurse().getElementsByQuerySelector('.tenor'))
+        [<music21.note.Note C>,
+         <music21.note.Note D>,
+         <music21.note.Note E>,
+         <music21.note.Note F>]
+
+        >>> list(s.recurse().getElementsByQuerySelector('Rest'))
+        [<music21.note.Rest quarter>,
+         <music21.note.Rest whole>]
+
+        Note that unlike with stream slices, the querySelector does not do anything special
+        for id searches.  `.first()` will need to be called to find the element (if any)
+
+        >>> s.recurse().getElementsByQuerySelector('#last').first()
+        <music21.note.Note F>
+
+        New in v.7
+        '''
+        if querySelector.startswith('#'):
+            return self.addFilter(filters.IdFilter(querySelector[1:]), returnClone=returnClone)
+        if querySelector.startswith('.'):
+            return self.addFilter(filters.GroupFilter(querySelector[1:]), returnClone=returnClone)
+        return self.addFilter(filters.ClassFilter(querySelector), returnClone=returnClone)
+
+
     def getElementsNotOfClass(self, classFilterList, *, returnClone=True):
         '''
         Adds a filter, removing all Elements that do not
@@ -1280,7 +1341,7 @@ class StreamIterator(prebase.ProtoM21Object):
         >>> for el in s.iter.notesAndRests:
         ...     print(el)
         <music21.note.Note C>
-        <music21.note.Rest rest>
+        <music21.note.Rest quarter>
         <music21.note.Note D>
 
         chained filters... (this makes no sense since notes is a subset of notesAndRests)
diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py
index 54d1655fc..e8b268fe0 100644
--- a/music21/stream/makeNotation.py
+++ b/music21/stream/makeNotation.py
@@ -726,7 +726,7 @@ def makeRests(
     >>> b.lowestOffset
     0.0
     >>> b.show('text')
-    {0.0} <music21.note.Rest rest>
+    {0.0} <music21.note.Rest 20ql>
     {20.0} <music21.note.Note C>
     >>> b[0].duration.quarterLength
     20.0
@@ -749,9 +749,9 @@ def makeRests(
     >>> b.lowestOffset
     0.0
     >>> b.show('text')
-    {0.0} <music21.note.Rest rest>
+    {0.0} <music21.note.Rest 20ql>
     {20.0} <music21.note.Note C>
-    {21.0} <music21.note.Rest rest>
+    {21.0} <music21.note.Rest 9ql>
     {30.0} <music21.note.Note D>
     >>> b[0].style.hideObjectOnPrint
     True
@@ -781,13 +781,13 @@ def makeRests(
     {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>
+        {0.0 - 4.0} <music21.note.Rest whole>
     {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>
+        {1.0 - 4.0} <music21.note.Rest dotted-half>
     {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>
+        {1.0 - 4.0} <music21.note.Rest dotted-half>
         {4.0 - 4.0} <music21.bar.Barline type=final>
 
     Changed in v6 -- all but first attribute are keyword only
@@ -878,7 +878,7 @@ def makeRests(
 
     if returnObj.hasMeasures():
         # split rests at measure boundaries
-        returnObj.makeTies(classFilterList=(note.Rest,))
+        returnObj.makeTies(classFilterList=(note.Rest,), inPlace=True)
 
         # reposition measures
         accumulatedTime = 0.0
@@ -988,11 +988,11 @@ def makeTies(
     {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>
+        {0.0} <music21.note.Rest whole>
     {4.0} <music21.stream.Measure 2 offset=4.0>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest whole>
     {8.0} <music21.stream.Measure 3 offset=8.0>
-        {0.0} <music21.note.Rest rest>
+        {0.0} <music21.note.Rest whole>
         {4.0} <music21.bar.Barline type=final>
 
     Notes: uses base.Music21Object.splitAtQuarterLength() once it has figured out
@@ -1019,7 +1019,7 @@ def makeTies(
     {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>
+        {0.0 - 3.0} <music21.note.Rest dotted-half>
     {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>
diff --git a/music21/tinyNotation.py b/music21/tinyNotation.py
index 30052c77a..cd9b60020 100644
--- a/music21/tinyNotation.py
+++ b/music21/tinyNotation.py
@@ -58,7 +58,7 @@ Here is an example of TinyNotation in action.
     {0.0} <music21.clef.TrebleClef>
     {0.0} <music21.meter.TimeSignature 3/4>
     {0.0} <music21.note.Note E>
-    {1.0} <music21.note.Rest rest>
+    {1.0} <music21.note.Rest quarter>
     {2.0} <music21.note.Note F#>
 {3.0} <music21.stream.Measure 2 offset=3.0>
     {0.0} <music21.note.Note G>
@@ -230,6 +230,7 @@ import collections
 import copy
 import re
 import sre_parse
+import typing
 import unittest
 
 from music21 import note
@@ -751,6 +752,120 @@ class NoteToken(NoteOrRestToken):
         return t
 
 
+def _getDefaultTokenMap() -> typing.List[
+        typing.Tuple[
+            str,
+            typing.Type[Token]
+        ]
+]:
+    """
+    Returns the default tokenMap for TinyNotation.
+
+    Based on the following grammar (in Extended Backus-Naur form)
+    (https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form)
+
+    (* Items in parentheses are grouped *)
+    (* Items in curly braces appear zero or more times *)
+    (* Items in square brackets may appear exactly zero or one time *)
+    (* Items in double quotes are literal strings *)
+    (* Items between question marks should be interpreted as English *)
+    (* Each rule is ended by a semicolon *)
+
+    TINY-NOTATION = TOKEN, { WHITESPACE, TOKEN } ;
+    WHITESPACE = ( " " | ? Carriage return ? ) , { " " | ? Carriage return ? } ;
+    TOKEN = ( TIME-SIGNATURE | TUPLET | REST | NOTE );
+    TIME-SIGNATURE = INTEGER, "/", INTEGER ;
+    INTEGER = DIGIT, { DIGIT } ;
+    DIGIT = ( "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ) ;
+    TUPLET = ( "trip" | "quad" | ALPHANUMERIC ), "{",
+        [ WHITESPACE ],
+        ( REST | NOTE ),
+        { WHITESPACE, ( REST | NOTE ) },
+        [ WHITESPACE ],
+    "}" ;
+    REST = "r", [ DURATION ], [ MODIFIER ] ;
+    DURATION = ( EVEN-NUMBER, { "." } | { "." }, EVEN-NUMBER | ".", { "." } ) ;
+    EVEN-NUMBER = { INTEGER }, ( "0" | "2" | "4" | "6" | "8" ) ;
+    NOTE = PITCH, [ DURATION ], [ TIE ], { MODIFIER } ;
+    PITCH = (
+        ( LOW-A | LOW-B | LOW-C | LOW-D | LOW-E | LOW-F | LOW-G ), [ ACCIDENTAL ] |
+        ( "a" | "b" | "c" | "d" | "e" | "f" | "g" ), [ ACCIDENTAL ], { "'" } |
+        ( "a" | "b" | "c" | "d" | "e" | "f" | "g" ), { "'" }, [ ACCIDENTAL ]
+    ) ;
+    LOW-A = "A", { "A" } ;
+    LOW-B = "B", { "B" } ;
+    LOW-C = "C", { "C" } ;
+    LOW-D = "D", { "D" } ;
+    LOW-E = "E", { "E" } ;
+    LOW-F = "F", { "F" } ;
+    LOW-G = "G", { "G" } ;
+    ACCIDENTAL = ( EDITORIAL | SHARPS | FLATS | NATURAL ) ;
+    EDITORIAL = "(", ( SHARPS | FLATS | NATURAL ), ")" ;
+    SHARPS = "#", { "#" } ;
+    FLATS = "-", { "-" } ;
+    NATURAL = "n" ;
+    TIE = "~" ;
+    MODIFIER = (
+        EQUALS-MODIFIER |
+        UNDERSCORE-MODIFIER |
+        SQUARE-MODIFIER |
+        ANGLE-MODIFIER |
+        PARENS-MODIFIER |
+        STAR-MODIFIER
+    ) ;
+    EQUALS-MODIFIER = "=", EQUALS-DATA ;
+    UNDERSCORE-MODIFIER = "_", UNDERSCORE-DATA ;
+    SQUARE-MODIFIER = "[", ALPHANUMERIC, "]" ;
+    ANGLE-MODIFIER = "<", ALPHANUMERIC, ">" ;
+    PARENS-MODIFIER = "(", ALPHANUMERIC, ")" ;
+    STAR-MODIFIER = "*", ALPHANUMERIC, "*" ;
+    (* The following is just shorthand. *)
+    ALPHANUMERIC = ? At least one alphanumeric character. So "a-z", "A-Z", or "0-9" ? ;
+    EQUALS-DATA = ? At least one non-whitespace, non-"_" character. ? ;
+    UNDERSCORE-DATA = ? At least one non-whitespace, non-"=" character. ? ;
+    """
+    sharpsFlatsOrNaturalRegex = r'#+|-+|n'
+    editorialRegex = fr'\((?:{sharpsFlatsOrNaturalRegex})\)'
+    accidentalRegex = fr'{editorialRegex}|(?:{sharpsFlatsOrNaturalRegex})'
+
+    lowNoteRegex = fr'(?:A+|B+|C+|D+|E+|F+|G+)(?:{accidentalRegex})?'
+    highNoteRegex = (
+        r'(?:a|b|c|d|e|f|g)'
+        + fr"(?:(?:{accidentalRegex})?'*|'*(?:{accidentalRegex})?)"
+    )
+    noteNameRegex = fr'{lowNoteRegex}|{highNoteRegex}'
+
+    durationRegex = r'\d+\.*|\.*\d+|\.+'
+
+    tieStateRegex = r'~'
+
+    equalsRegex = r'=[^\s_]*'
+    starRegex = r'\*.*?\*'
+    angleRegex = r'<.*?>'
+    parensRegex = r'\(.*?\)'
+    squareRegex = r'\[.*?]'
+    underscoreRegex = r'_[^\s=]'
+    modifierRegex = (
+        fr'{equalsRegex}|{starRegex}|{angleRegex}|'
+        + fr'{parensRegex}|{squareRegex}|{underscoreRegex}'
+    )
+
+    return [
+        (r'^(\d+\/\d+)$', TimeSignatureToken),
+        (
+            fr'^r((?:{durationRegex})?(?:{modifierRegex})*)$',
+            RestToken
+        ),
+        (
+            (
+                fr'^((?:{noteNameRegex})(?:{durationRegex})?'
+                + fr'(?:{tieStateRegex})?(?:{modifierRegex})*)$'
+            ),
+            NoteToken
+        ),  # last
+    ]
+
+
 class Converter:
     '''
     Main conversion object for TinyNotation.
@@ -942,11 +1057,7 @@ class Converter:
         self.generalBracketStateRe = re.compile(r'(\w+){')
         self.tieStateRe = re.compile(r'~')
 
-        self.tokenMap = [
-            (r'(\d+\/\d+)', TimeSignatureToken),
-            (r'r(\S*)', RestToken),
-            (r'([a-gA-G]\S*)', NoteToken),  # last
-        ]
+        self.tokenMap = _getDefaultTokenMap()
         self.modifierEquals = IdModifier
         self.modifierStar = None
         self.modifierAngle = None
@@ -1238,7 +1349,7 @@ class Converter:
 class Test(unittest.TestCase):
     parseTest = '1/4 trip{C8~ C~_hello C=mine} F~ F~ 2/8 F F# quad{g--16 a## FF(n) g#} g16 F0'
 
-    def testOne(self):
+    def testOne(self) -> None:
         c = Converter(self.parseTest)
         c.parse()
         s = c.stream
@@ -1256,6 +1367,97 @@ class Test(unittest.TestCase):
         self.assertEqual(sfn[12].duration.quarterLength, 1.0)
         self.assertEqual(sfn[12].expressions[0].classes, expressions.Fermata().classes)
 
+    def testRaiseExceptions(self) -> None:
+        error_states = [
+            {
+                'string': 'h',
+                'reason': 'h is not a valid note',
+            },
+            {
+                'string': 'a;',
+                'reason': 'a semicolon is not a valid character or modifier',
+            },
+            {
+                'string': 'r;',
+                'reason': 'a semicolon is not a valid character or modifier',
+            },
+            {
+                'string': '4/4;',
+                'reason': 'a semicolon is not a valid character or modifier',
+            },
+            {
+                'string': 'ABC',
+                'reason': (
+                    'only the same upper-cased letter may be repeated to '
+                    + 'indicate lower octaves'
+                ),
+            },
+            {
+                'string': 'aaa',
+                'reason': (
+                    'the same lower-cased letter may not be repeated to '
+                    + 'indicate higher octaves. Instead use apostrophes.'
+                ),
+            },
+        ]
+
+        for error_state in error_states:
+            with self.assertRaises(TinyNotationException, msg=(
+                    'Should have raised a TinyNotationException for input '
+                    + f"'{error_state['string']}' because {error_state['reason']}."
+            )):
+                converter = Converter(error_state['string'], raiseExceptions=True)
+                converter.parse()
+
+    def testGetDefaultTokenMap(self) -> None:
+        defaultTokenMap = _getDefaultTokenMap()
+
+        self.assertEqual(
+            len(defaultTokenMap),
+            3,
+            (
+                'There should be three valid token types by default: Time '
+                + 'signatures, Notes, and Rests'
+            )
+        )
+
+        validTokenTypeCounts = {
+            NoteToken: 0,
+            RestToken: 0,
+            TimeSignatureToken: 0,
+        }
+
+        for regex, tokenType in defaultTokenMap:
+            self.assertIn(
+                tokenType,
+                validTokenTypeCounts,
+                (
+                    'Found unexpected token type in default token map:'
+                    + f'{tokenType.__class__.__name__}.'
+                )
+            )
+            validTokenTypeCounts[tokenType] += 1
+            self.assertGreater(
+                len(regex),
+                0,
+                (
+                    'Should provide a non-empty string for the regular '
+                    + 'expression in the default token map for tokens of type '
+                    + f'{tokenType.__class__.__name__}.'
+                )
+            )
+
+        for tokenType in validTokenTypeCounts:
+            self.assertEqual(
+                validTokenTypeCounts[tokenType],
+                1,
+                (
+                    'Should have found each valid token type exactly once in '
+                    + 'the default token map.'
+                )
+            )
+
+
 
 class TestExternal(unittest.TestCase):  # pragma: no cover
 
diff --git a/music21/tree/trees.py b/music21/tree/trees.py
index 303bcdcf3..4bf0af5d7 100644
--- a/music21/tree/trees.py
+++ b/music21/tree/trees.py
@@ -978,8 +978,7 @@ class OffsetTree(ElementTree):
                 result.extend(recurseBySlice(node.leftChild, start, stop))
             if start < node.payloadElementsStopIndex and node.payloadElementsStartIndex < stop:
                 indexStart = start - node.payloadElementsStartIndex
-                if indexStart < 0:
-                    indexStart = 0
+                indexStart = max(indexStart, 0)
                 indexStop = stop - node.payloadElementsStartIndex
                 result.extend(node.payload[indexStart:indexStop])
             if node.payloadElementsStopIndex <= stop and node.rightChild:
diff --git a/music21/tree/verticality.py b/music21/tree/verticality.py
index fd2908e5a..1084acd19 100644
--- a/music21/tree/verticality.py
+++ b/music21/tree/verticality.py
@@ -570,7 +570,7 @@ class Verticality(prebase.ProtoM21Object):
         <music21.tree.verticality.Verticality 400.0 {}>
         >>> el = verticality.makeElement(1/3)
         >>> el
-        <music21.note.Rest rest>
+        <music21.note.Rest 1/3ql>
         >>> el.duration.fullName
         'Eighth Triplet (1/3 QL)'
 
diff --git a/music21/variant.py b/music21/variant.py
index 8bc9bca7b..7aa3a828d 100644
--- a/music21/variant.py
+++ b/music21/variant.py
@@ -171,15 +171,11 @@ def mergeVariantScores(aScore, vScore, variantName='variant', *, inPlace=False):
 
     >>> aScore, vScore = stream.Score(), stream.Score()
 
-    >>> ap1 = stream.Part(converter.parse('tinynotation: 4/4   a4 b c d    e2 f2   g2 f4 g4 '
-    ...                                   ).makeMeasures())
-    >>> vp1 = stream.Part(converter.parse('tinynotation: 4/4   a4 b c e    e2 f2   g2 f4 a4 '
-    ...                                   ).makeMeasures())
+    >>> ap1 = converter.parse('tinynotation: 4/4   a4 b c d    e2 f2   g2 f4 g4 ')
+    >>> vp1 = converter.parse('tinynotation: 4/4   a4 b c e    e2 f2   g2 f4 a4 ')
 
-    >>> ap2 = stream.Part(converter.parse('tinynotation: 4/4   a4 g f e    f2 e2   d2 g4 f4 '
-    ...                                   ).makeMeasures())
-    >>> vp2 = stream.Part(converter.parse('tinynotation: 4/4   a4 g f e    f2 g2   f2 g4 d4 '
-    ...                                   ).makeMeasures())
+    >>> ap2 = converter.parse('tinynotation: 4/4   a4 g f e    f2 e2   d2 g4 f4 ')
+    >>> vp2 = converter.parse('tinynotation: 4/4   a4 g f e    f2 g2   f2 g4 d4 ')
 
     >>> aScore.insert(0.0, ap1)
     >>> aScore.insert(0.0, ap2)
@@ -926,7 +922,7 @@ def addVariant(
     {0.0} <music21.note.Note E>
     {1.0} <music21.note.Note E>
     {2.0} <music21.note.Note E>
-    {3.0} <music21.variant.Variant object of length 0.0>
+    {3.0} <music21.variant.Variant object of length 6.0>
     {3.0} <music21.note.Note E>
     {4.0} <music21.note.Note E>
     {5.0} <music21.note.Note E>
@@ -947,12 +943,7 @@ def addVariant(
             tempVariant.append(sVariant)
         else:  # sVariant is not a measure
             sVariantMeasures = sVariant.getElementsByClass('Measure')
-            # apparently expression cannot be simplified. -- this is a mistake
-            # since sVariantMeasures will never == [] even if there are no measures.
-            # yet switching this to `if not sVariantMeasures` breaks things.
-            # TODO(msc) -- figure this out and fix it.
-            # noinspection PySimplifyBooleanCheck
-            if sVariantMeasures == []:  # If there are no measures, work element-wise
+            if not sVariantMeasures:  # If there are no measures, work element-wise
                 for e in sVariant:
                     offset = e.getOffsetBySite(sVariant) + startOffset
                     tempVariant.insert(offset, e)
@@ -1664,9 +1655,7 @@ def makeAllVariantsReplacements(streamWithVariants,
     >>> s2.makeMeasures(inPlace=True)
     >>> variant.mergeVariants(s, s2, variantName='london', inPlace=True)
 
-    >>> newPart = stream.Part(s)
-    >>> newStream = stream.Score()
-    >>> newStream.append(newPart)
+    >>> newStream = stream.Score(s)
 
     >>> returnStream = variant.makeAllVariantsReplacements(newStream, recurse=False)
     >>> for v in returnStream.parts[0].variants:
@@ -2370,6 +2359,8 @@ class Variant(base.Music21Object):
             {2.0} <music21.note.Note A>
             {3.0} <music21.note.Note B>
         '''
+        spacerFilter = lambda r: r.hasStyleInformation and r.style.hideObjectOnPrint
+
         if contextStream is None:
             contextStream = self.activeSite
             if contextStream is None:
@@ -2385,7 +2376,10 @@ class Variant(base.Music21Object):
         vStart = self.getOffsetBySite(contextStream)
 
         if includeSpacers is True:
-            spacerDuration = self.getElementsByClass('SpacerRest').first().duration.quarterLength
+            spacerDuration = (self
+                              .getElementsByClass('Rest')
+                              .addFilter(spacerFilter)
+                              .first().duration.quarterLength)
         else:
             spacerDuration = 0.0
 
@@ -2584,7 +2578,9 @@ class Test(unittest.TestCase):
         vn1 = note.Note('F#4')
         vn2 = note.Note('A-4')
 
-        v1 = Variant([vn1, vn2])
+        v1 = Variant()
+        v1.insert(0, vn1)
+        v1.insert(0, vn2)
         v1Copy = copy.deepcopy(v1)
         # copies stored objects; they point to the different Notes vn1/vn2
         self.assertIsNot(v1Copy[0], v1[0])
@@ -2614,7 +2610,9 @@ class Test(unittest.TestCase):
         s.repeatAppend(note.Note('G4'), 8)
         vn1 = note.Note('F#4')
         vn2 = note.Note('A-4')
-        v1 = Variant([vn1, vn2])
+        v1 = Variant()
+        v1.insert(0, vn1)
+        v1.insert(0, vn2)
         s.insert(5, v1)
 
         # as we deepcopy the elements in the variants, we have new Notes


Test patch:
diff --git a/music21/mei/test_base.py b/music21/mei/test_base.py
index 07ca8db2e..b8f01d057 100644
--- a/music21/mei/test_base.py
+++ b/music21/mei/test_base.py
@@ -1408,29 +1408,21 @@ class Test(unittest.TestCase):
         self.assertEqual('the id', actual.id)
         self.assertEqual('start', actual.duration.tuplets[0].type)
 
-    @mock.patch('music21.note.SpacerRest')
-    @mock.patch('music21.mei.base.makeDuration')
-    @mock.patch('music21.mei.base.scaleToTuplet')
-    def testUnit2TestRestFromElement(self, mockTuplet, mockMakeDur, mockSpacer):
+    def testUnit2TestRestFromElement(self):
         '''
         spaceFromElement(): test @dur, @dots, @xml:id, and tuplet-related attributes
         '''
-        elem = ETree.Element('rest', attrib={'dur': '4', 'dots': '1', _XMLID: 'the id',
-                                             'm21TupletNum': '5', 'm21TupletNumbase': '4',
-                                             'm21TupletType': 'start'})
-        mockMakeDur.return_value = 'the duration'
-        mockNewSpace = mock.MagicMock('new rest')
-        mockSpacer.return_value = mockNewSpace
-        mockTuplet.return_value = 'tupletized'
-        expected = mockTuplet.return_value
-
+        elem = ETree.Element('rest', attrib={'dur': '4',
+                                             'dots': '1',
+                                             _XMLID: 'the id',
+                                             'm21TupletNum': '5',
+                                             'm21TupletNumbase': '4',
+                                             'm21TupletType': 'start',
+                                             })
         actual = base.spaceFromElement(elem)
-
-        self.assertEqual(expected, actual)
-        mockSpacer.assert_called_once_with(duration=mockMakeDur.return_value)
-        mockMakeDur.assert_called_once_with(1.0, 1)
-        mockTuplet.assert_called_once_with(mockSpacer.return_value, elem)
-        self.assertEqual('the id', mockNewSpace.id)
+        self.assertIsInstance(actual, note.Rest)
+        self.assertTrue(actual.style.hideObjectOnPrint)
+        self.assertEqual('the id', actual.id)
 
     def testIntegration2TestRestFromElement(self):
         '''
@@ -1450,30 +1442,30 @@ class Test(unittest.TestCase):
         self.assertEqual('start', actual.duration.tuplets[0].type)
 
     @mock.patch('music21.mei.base.restFromElement')
-    def testUnit3TestRestFromElement(self, mockRest):
+    def testUnit3TestRestFromElement(self, mockRestFromElement):
         '''
         mRestFromElement(): reacts properly to an Element with the @dur attribute
         '''
         elem = ETree.Element('mRest', attrib={'dur': '2'})
-        mockRest.return_value = 'the rest'
+        mockRestFromElement.return_value = 'the rest'
 
         actual = base.mRestFromElement(elem)
 
-        self.assertEqual(mockRest.return_value, actual)
-        mockRest.assert_called_once_with(elem, None)
+        self.assertEqual(mockRestFromElement.return_value, actual)
+        mockRestFromElement.assert_called_once_with(elem, None)
 
     @mock.patch('music21.mei.base.restFromElement')
-    def testUnit4TestRestFromElement(self, mockRest):
+    def testUnit4TestRestFromElement(self, mockRestFromElement):
         '''
         mRestFromElement(): reacts properly to an Element without the @dur attribute
         '''
         elem = ETree.Element('mRest')
-        mockRest.return_value = mock.MagicMock()
+        mockRestFromElement.return_value = mock.MagicMock()
 
         actual = base.mRestFromElement(elem)
 
-        self.assertEqual(mockRest.return_value, actual)
-        mockRest.assert_called_once_with(elem, None)
+        self.assertEqual(mockRestFromElement.return_value, actual)
+        mockRestFromElement.assert_called_once_with(elem, None)
         self.assertTrue(actual.m21wasMRest)
 
     @mock.patch('music21.mei.base.spaceFromElement')
diff --git a/music21/musicxml/testTab.xml b/music21/musicxml/testTab.xml
index df63b5fe4..7a81ec482 100644
--- a/music21/musicxml/testTab.xml
+++ b/music21/musicxml/testTab.xml
@@ -9,6 +9,12 @@
     </part-list>
     <part id="P1">
         <measure number="0">
+            <attributes>
+                <time>
+                    <beats>4</beats>
+                    <beat-type>4</beat-type>
+                </time>
+            </attributes>
             <note>
                 <type>quarter</type>
                 <notations>
diff --git a/music21/stream/tests.py b/music21/stream/tests.py
index eb18e713d..d6cf35b51 100644
--- a/music21/stream/tests.py
+++ b/music21/stream/tests.py
@@ -619,18 +619,18 @@ class Test(unittest.TestCase):
         a = converter.parse(thisWork)
 
         b = a[7][5:10]
-        environLocal.printDebug(['b', b, b.sites.getSiteIds()])
+        # environLocal.printDebug(['b', b, b.sites.getSiteIds()])
         c = a[7][10:15]
-        environLocal.printDebug(['c', c, c.sites.getSiteIds()])
+        # environLocal.printDebug(['c', c, c.sites.getSiteIds()])
         d = a[7][15:20]
-        environLocal.printDebug(['d', d, d.sites.getSiteIds()])
+        # environLocal.printDebug(['d', d, d.sites.getSiteIds()])
 
         s2 = Stream()
-        environLocal.printDebug(['s2', s2, id(s2)])
+        # environLocal.printDebug(['s2', s2, id(s2)])
 
-        s2.insert(b)
-        s2.insert(c)
-        s2.insert(d)
+        for stream_case in (b, c, d):
+            for e in stream_case:
+                s2.insert(e.offset, e)
 
     def testActiveSites(self):
         '''
@@ -1933,29 +1933,30 @@ class Test(unittest.TestCase):
 
         match = str([(n.offset, n, n.duration) for n in m2.flat.notesAndRests])
         self.assertEqual(match,
-                         '[(0.0, <music21.note.Rest rest>, <music21.duration.Duration 1.0>), '
+                         '[(0.0, <music21.note.Rest quarter>, <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>)]')
+                         + '(2.0, <music21.note.Rest half>, <music21.duration.Duration 2.0>)]')
 
         match = str([(n.offset, n, n.duration) for n in m2.flat])
         self.assertEqual(match,
-                         '[(0.0, <music21.note.Rest rest>, <music21.duration.Duration 1.0>), '
+                         '[(0.0, <music21.note.Rest quarter>, <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>)]')
+                         + '(2.0, <music21.note.Rest half>, <music21.duration.Duration 2.0>)]')
 
         # m2.show()
 
         match = str(list(s.flat.notesAndRests))
-        self.assertEqual(match, '[<music21.note.Rest rest>, <music21.note.Note C>, '
-                                  + '<music21.note.Rest rest>, <music21.note.Rest rest>, '
-                                  + '<music21.note.Note C>, <music21.note.Rest rest>]')
+        self.assertEqual(match, '[<music21.note.Rest half>, <music21.note.Note C>, '
+                                  + '<music21.note.Rest quarter>, <music21.note.Rest quarter>, '
+                                  + '<music21.note.Note C>, <music21.note.Rest half>]')
         match = str([(n, n.duration) for n in s.flat.notesAndRests])
-        self.assertEqual(match, '[(<music21.note.Rest rest>, <music21.duration.Duration 2.0>), '
-                                  + '(<music21.note.Note C>, <music21.duration.Duration 1.0>), '
-                                  + '(<music21.note.Rest rest>, <music21.duration.Duration 1.0>), '
-                                  + '(<music21.note.Rest rest>, <music21.duration.Duration 1.0>), '
-                                  + '(<music21.note.Note C>, <music21.duration.Duration 1.0>), '
-                                  + '(<music21.note.Rest rest>, <music21.duration.Duration 2.0>)]')
+        self.assertEqual(match,
+                         '[(<music21.note.Rest half>, <music21.duration.Duration 2.0>), '
+                          + '(<music21.note.Note C>, <music21.duration.Duration 1.0>), '
+                          + '(<music21.note.Rest quarter>, <music21.duration.Duration 1.0>), '
+                          + '(<music21.note.Rest quarter>, <music21.duration.Duration 1.0>), '
+                          + '(<music21.note.Note C>, <music21.duration.Duration 1.0>), '
+                          + '(<music21.note.Rest half>, <music21.duration.Duration 2.0>)]')
 
         GEX = m21ToXml.GeneralObjectExporter()
         unused_mx = GEX.parse(s).decode('utf-8')
@@ -2929,7 +2930,8 @@ class Test(unittest.TestCase):
         '''
         src = corpus.parse('bach/bwv324.xml')
         # get some measures of the soprano; just get the notes
-        ex = src.parts[0].flat.notesAndRests.stream()[0:30]
+        found = src.parts.first().flat.notesAndRests[0:30]
+        ex = Part(found)
 
         self.assertEqual(ex.highestOffset, 38.0)
         self.assertEqual(ex.highestTime, 42.0)
@@ -2957,7 +2959,8 @@ class Test(unittest.TestCase):
         src = corpus.parse('bach/bwv324.xml')
         # get some measures of the soprano; just get the notes
         # environLocal.printDebug(['testAugmentOrDiminishCorpus()', 'extracting notes:'])
-        ex = src.parts[0].flat.notesAndRests.stream()[0:30]
+        found = src.parts.first().flat.notesAndRests[0:30]
+        ex = Part(found)
         # attach a couple of transformations
         s = Score()
         for scalar in [0.5, 1.5, 2, 0.25]:
@@ -5455,16 +5458,20 @@ class Test(unittest.TestCase):
 
         sPost = s.makeRests(fillGaps=True, inPlace=False)
         self.assertEqual(str(list(sPost.voices[0].notesAndRests)),
-                         '[<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>]')
+                         '[<music21.note.Rest half>, <music21.note.Note C>, '
+                         + '<music21.note.Rest 2.25ql>, '
+                         + '<music21.note.Note C>, '
+                         + '<music21.note.Rest 2.5ql>, '
+                         + '<music21.note.Note C>, '
+                         + '<music21.note.Rest 4.25ql>, '
+                         + '<music21.note.Note C>, '
+                         + '<music21.note.Rest half>]')
         self.assertEqual(str(list(sPost.voices[1].notesAndRests)),
-                         '[<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>, '
-                         + '<music21.note.Rest rest>, <music21.note.Note C>]')
+                         '[<music21.note.Rest 16th>, <music21.note.Note C>, '
+                         + '<music21.note.Rest 3.25ql>, '
+                         + '<music21.note.Note C>, '
+                         + '<music21.note.Rest dotted-quarter>, <music21.note.Note C>, '
+                         + '<music21.note.Rest breve>, <music21.note.Note C>]')
 
         # sPost.show()
 
@@ -6546,11 +6553,11 @@ class Test(unittest.TestCase):
         self.assertEqual(match, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 25.0])
 
         # s.elements = s1
-        s3 = s1[:]
+        s3 = Stream(s1[:])
         match = []
         for e in s3.elements:
             match.append(e.getOffsetBySite(s3))
-        self.assertEqual(match, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
+        self.assertEqual(match, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 25.0])
 
         # this resets active site, so we get the right offsets on element
         # assignment
@@ -7927,6 +7934,14 @@ class Test(unittest.TestCase):
             # print(note1.id, note2.id, note3.id, note4.id)
         # TEST???
 
+    def testCoreGuardBeforeAddElement(self):
+        n = note.Note()
+        s = Stream()
+        with self.assertRaises(StreamException):
+            Stream([n, n])
+        with self.assertRaises(StreamException):
+            Stream([n, None, s.iter])
+
     # REMOVED: Turns out that it DOES have fermata on every note!
     # def testSchoenbergChordifyFermatas(self):
     #     '''
diff --git a/music21/test/multiprocessTest.py b/music21/test/multiprocessTest.py
index 8a54e800a..f2977b522 100644
--- a/music21/test/multiprocessTest.py
+++ b/music21/test/multiprocessTest.py
@@ -150,73 +150,72 @@ def mainPoolRunner(testGroup=('test',), restoreEnvironmentDefaults=False, leaveO
     pathsToRun = modGather.modulePaths  # [30:60]
 
     # pylint: disable=not-callable
-    pool = multiprocessing.Pool(processes=poolSize)
-
-    # imap returns the results as they are completed.  Since the number of files is small,
-    # the overhead of returning is outweighed by the positive aspect of getting results immediately
-    # unordered says that results can RETURN in any order; not that they'd be pooled out in any
-    # order.
-    res = pool.imap_unordered(runOneModuleWithoutImp,
-                              ((modGather, fp) for fp in pathsToRun))
-
-    continueIt = True
-    timeouts = 0
-    eventsProcessed = 0
-    summaryOutput = []
-
-    while continueIt is True:
-        try:
-            newResult = res.next(timeout=1)
-            if timeouts >= 5:
-                print('')
-            if newResult is not None:
-                if newResult.moduleName is not None:
-                    mn = newResult.moduleName
-                    mn = mn.replace('___init__', '')
-                    mn = mn.replace('_', '.')
-                else:
-                    mn = ''
-                rt = newResult.runTime
-                if rt is not None:
-                    rt = round(newResult.runTime * 10) / 10.0
-                    if not newResult.errors and not newResult.failures:
-                        print(f'\t\t\t\t{mn}: {newResult.testsRun} tests in {rt} secs')
+    with multiprocessing.Pool(processes=poolSize) as pool:
+
+        # imap returns the results as they are completed.
+        # Since the number of files is small, the overhead of returning is
+        # outweighed by the positive aspect of getting results immediately
+        # unordered says that results can RETURN in any order; not that
+        # they'd be pooled out in any order.
+        res = pool.imap_unordered(runOneModuleWithoutImp,
+                                    ((modGather, fp) for fp in pathsToRun))
+
+        continueIt = True
+        timeouts = 0
+        eventsProcessed = 0
+        summaryOutput = []
+
+        while continueIt is True:
+            try:
+                newResult = res.next(timeout=1)
+                if timeouts >= 5:
+                    print('')
+                if newResult is not None:
+                    if newResult.moduleName is not None:
+                        mn = newResult.moduleName
+                        mn = mn.replace('___init__', '')
+                        mn = mn.replace('_', '.')
                     else:
-                        print('\t\t\t\t{0}: {1} tests, {2} errors {3} failures in {4} secs'.format(
-                            mn,
-                            newResult.testsRun,
-                            len(newResult.errors),
-                            len(newResult.failures),
-                            rt))
-            timeouts = 0
-            eventsProcessed += 1
-            summaryOutput.append(newResult)
-        except multiprocessing.TimeoutError:
-            timeouts += 1
-            if timeouts == 5 and eventsProcessed > 0:
-                print('Delay in processing, seconds: ', end='')
-            elif timeouts == 5:
-                print('Starting first modules, should take 5-10 seconds: ', end='')
-
-            if timeouts % 5 == 0:
-                print(str(timeouts) + ' ', end='', flush=True)
-            if timeouts > maxTimeout and eventsProcessed > 0:
-                print('\nToo many delays, giving up...', flush=True)
+                        mn = ''
+                    rt = newResult.runTime
+                    if rt is not None:
+                        rt = round(newResult.runTime * 10) / 10.0
+                        if not newResult.errors and not newResult.failures:
+                            print(f'\t\t\t\t{mn}: {newResult.testsRun} tests in {rt} secs')
+                        else:
+                            numErr = len(newResult.errors)
+                            numFail = len(newResult.failures)
+                            print(f'\t\t\t\t{mn}: {newResult.testsRun} tests, '
+                                  f'{numErr} errors {numFail} failures in {rt} secs')
+                timeouts = 0
+                eventsProcessed += 1
+                summaryOutput.append(newResult)
+            except multiprocessing.TimeoutError:
+                timeouts += 1
+                if timeouts == 5 and eventsProcessed > 0:
+                    print('Delay in processing, seconds: ', end='')
+                elif timeouts == 5:
+                    print('Starting first modules, should take 5-10 seconds: ', end='')
+
+                if timeouts % 5 == 0:
+                    print(str(timeouts) + ' ', end='', flush=True)
+                if timeouts > maxTimeout and eventsProcessed > 0:
+                    print('\nToo many delays, giving up...', flush=True)
+                    continueIt = False
+                    printSummary(summaryOutput, timeStart, pathsToRun)
+                    pool.close()
+                    sys.exit()
+            except StopIteration:
                 continueIt = False
-                printSummary(summaryOutput, timeStart, pathsToRun)
                 pool.close()
-                sys.exit()
-        except StopIteration:
-            continueIt = False
-            pool.close()
-            pool.join()
-        except Exception as excp:  # pylint: disable=broad-except
-            eventsProcessed += 1
-            exceptionLog = ModuleResponse(
-                returnCode='UntrappedException',
-                moduleName=str(excp)
-            )
-            summaryOutput.append(exceptionLog)
+                pool.join()
+            except Exception as excp:  # pylint: disable=broad-except
+                eventsProcessed += 1
+                exceptionLog = ModuleResponse(
+                    returnCode='UntrappedException',
+                    moduleName=str(excp)
+                )
+                summaryOutput.append(exceptionLog)
 
     sys.stderr = normalStdError
     printSummary(summaryOutput, timeStart, pathsToRun)