Repository: cuthbertLab/music21

Issue:
Providing a Chord to Stream constructor inserts each note separately
<!-- Version of music21:  print(music21.__version__) -->
**music21 version**

`6.7.1` and master, i.e. we didn't make this worse, but we have perhaps left the impression that this is safe

https://github.com/cuthbertLab/music21/blob/e030bee3c401c45ab616949ad5c464f2d8eb8a9f/music21/stream/base.py#L163

**Problem summary**
Providing a chord to the Stream constructor without wrapping in an iterable will not insert the chord but rather insert each note.  See remarks here on "other Stream". Chords are not streams:

https://github.com/cuthbertLab/music21/blob/e030bee3c401c45ab616949ad5c464f2d8eb8a9f/music21/stream/base.py#L118-L125

**Steps to reproduce**

```
c = chord.Chord('C2 A2')
s = stream.Stream(c)
s.show('text')
```

**Expected vs. actual behavior**
Expected: 
{0.0} <music21.chord.Chord C2 A2>

Actual:
{0.0} <music21.note.Note C>
{0.0} <music21.note.Note A>

**Suggested fix**

We can either focus on the Stream constructor and not iterate over `givenElements` if isinstance base.Music21Object, OR we can decide if `isIterable` should return True for music21Objects:

```
>>> c = chord.Chord('A2 C2')
>>> common.isIterable(c)
True
``` 

Comments:

Patch:
diff --git a/music21/stream/base.py b/music21/stream/base.py
index 314b2f099..cb9cce910 100644
--- a/music21/stream/base.py
+++ b/music21/stream/base.py
@@ -121,8 +121,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
     its :attr:`~music21.base.Music21Object.offset`
     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.
+    non-Measure Stream subclasses representing synchrony 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.
@@ -166,8 +167,8 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
     >>> 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:
+    New in v7 -- providing a list of objects or Measures or Scores (but not other Stream
+    subclasses such as Parts or Voices) now positions sequentially, i.e. appends:
 
     >>> s2 = stream.Measure([note.Note(), note.Note(), bar.Barline()])
     >>> s2.show('text')
@@ -197,11 +198,11 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
 
     Create nested streams in one fell swoop:
 
-    >>> s5 = stream.Score(stream.Part(stream.Measure(note.Note())))
+    >>> s5 = stream.Score(stream.Part(stream.Measure(chord.Chord('C2 A2'))))
     >>> s5.show('text')
     {0.0} <music21.stream.Part 0x...>
         {0.0} <music21.stream.Measure 0 offset=0.0>
-            {0.0} <music21.note.Note C>
+            {0.0} <music21.chord.Chord C2 A2>
     '''
     # this static attributes offer a performance boost over other
     # forms of checking class
@@ -278,18 +279,21 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
         if givenElements is None:
             return
 
-        if isinstance(givenElements, Stream) or not common.isIterable(givenElements):
+        if isinstance(givenElements, base.Music21Object) 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)
+        # but not if every element is a stream subclass other than a Measure or Score
+        # (i.e. Part or Voice generally, but even Opus theoretically)
+        # because these classes usually represent synchrony
         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):
+        if append and all(
+                (e.isStream and e.classSet.isdisjoint((Measure, Score)))
+                for e in givenElements):
             append = False
 
         for e in givenElements:


Test patch:
diff --git a/music21/stream/tests.py b/music21/stream/tests.py
index 9045b9334..e89122d5b 100644
--- a/music21/stream/tests.py
+++ b/music21/stream/tests.py
@@ -5344,6 +5344,16 @@ class Test(unittest.TestCase):
         s = o.getScoreByTitle(re.compile('Pfal(.*)'))
         self.assertEqual(s.metadata.title, 'Es fuhr sich ein Pfalzgraf')
 
+    def testOpusSequence(self):
+        '''
+        Providing a sequence of Scores to an Opus container should append
+        rather than insert each at 0.0.
+        '''
+        s1 = Score(Part(Measure(note.Note())))
+        s2 = Score(Part(Measure(note.Note())))
+        o = Opus([s1, s2])
+        self.assertEqual(o.elementOffset(s2), 1.0)
+
     def testActiveSiteMangling(self):
         outer = Stream()
         inner = Stream()