Repository: cuthbertLab/music21
Pull request #689 (2020-12-03 22:41:01)

Github on all versions, travis on 3.6 only


Patch:
diff --git a/.github/workflows/maincheck.yml b/.github/workflows/maincheck.yml
index af47ed88a..18b4dd013 100644
--- a/.github/workflows/maincheck.yml
+++ b/.github/workflows/maincheck.yml
@@ -10,18 +10,20 @@ on:
         - '*'
 
 jobs:
-  check38:
-    runs-on: ubuntu-18.04
+  run_tests:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9]
     steps:
-    - uses: actions/setup-python@v1
+    - uses: actions/setup-python@v2
       with:
-        python-version: '3.8'
-        architecture: 'x64'
+        python-version: ${{ matrix.python-version }}
     - uses: actions/checkout@v2
     - name: Install Ubuntu dependencies
       run: ./.github/scripts/install_ubuntu_deps.sh
     - name: Setup Lilypond
       run: python -c 'from music21 import environment; environment.UserSettings()["lilypondPath"] = "/home/runner/bin/lilypond"'
-    - name: Run Main script
+    - name: Run Main Test script
       run: python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'
 
diff --git a/.travis.yml b/.travis.yml
index e1d44cb22..aedc25a02 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,12 +5,6 @@ matrix:
         - python: 3.6
           dist: xenial
           sudo: false
-        - python: 3.7
-          dist: xenial
-          sudo: true
-        - python: 3.8
-          dist: xenial
-          sudo: true
 
 services:
   - xvfb
diff --git a/music21/midi/translate.py b/music21/midi/translate.py
index fb7a4265c..ceb8206b6 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -689,22 +689,39 @@ def midiEventsToInstrument(eventList):
     '''
     Convert a single MIDI event into a music21 Instrument object.
     '''
+    from music21 import midi as midiModule
+
     if not common.isListLike(eventList):
         event = eventList
     else:  # get the second event; first is delta time
         event = eventList[1]
 
     from music21 import instrument
+    decoded: str = ''
     try:
         if isinstance(event.data, bytes):
             # MuseScore writes MIDI files with null-terminated
             # instrument names.  Thus stop before the byte-0x0
             decoded = event.data.decode('utf-8').split('\x00')[0]
+            decoded = decoded.strip()
             i = instrument.fromString(decoded)
         else:
             i = instrument.instrumentFromMidiProgram(event.data)
     except (instrument.InstrumentException, UnicodeDecodeError):  # pragma: no cover
         i = instrument.Instrument()
+    # Set partName or instrumentName with literal value from parsing
+    if decoded:
+        # Except for lousy instrument names
+        if (
+            decoded.lower() in ('instrument', 'inst')
+            or decoded.lower().replace('instrument ', '').isdigit()
+            or decoded.lower().replace('inst ', '').isdigit()
+        ):
+            return i
+        elif event.type == midiModule.MetaEvents.SEQUENCE_TRACK_NAME:
+            i.partName = decoded
+        elif event.type == midiModule.MetaEvents.INSTRUMENT_NAME:
+            i.instrumentName = decoded
     return i
 
 
@@ -3218,6 +3235,12 @@ class Test(unittest.TestCase):
         self.assertIsInstance(instruments[0], instrument.Oboe)
         self.assertEqual(instruments[0].quarterLength, 0)
 
+        # Unrecognized instrument 'a'
+        dirLib = common.getSourceFilePath() / 'midi' / 'testPrimitive'
+        fp = dirLib / 'test15.mid'
+        s2 = converter.parse(fp)
+        self.assertEqual(s2.parts[0].partName, 'a')
+
     def testImportZeroDurationNote(self):
         '''
         Musescore places zero duration notes in multiple voice scenarios
@@ -3261,6 +3284,18 @@ class Test(unittest.TestCase):
         i = midiEventsToInstrument(event)
         self.assertIsInstance(i, instrument.Flute)
 
+    def testLousyInstrumentName(self):
+        from music21 import midi as midiModule
+
+        lousyNames = ('    ', 'Instrument 20', 'Instrument', 'Inst 2', 'instrument')
+        for name in lousyNames:
+            with self.subTest(name=name):
+                event = midiModule.MidiEvent()
+                event.data = bytes(name, 'utf-8')
+                event.type = midiModule.MetaEvents.INSTRUMENT_NAME
+                i = midiEventsToInstrument(event)
+                self.assertIsNone(i.instrumentName)
+
 
 # ------------------------------------------------------------------------------
 _DOC_ORDER = [streamToMidiFile, midiFileToStream]
diff --git a/music21/stream/__init__.py b/music21/stream/__init__.py
index 39a0d2346..b6fd35309 100644
--- a/music21/stream/__init__.py
+++ b/music21/stream/__init__.py
@@ -5756,6 +5756,9 @@ class Stream(core.StreamCoreMixin, base.Music21Object):
             groups = []
             for i, v in enumerate(srcObj.voices):
                 groups.append((v.flat, i))
+            elsNotOfVoice = srcObj.getElementsNotOfClass('Voice')
+            if len(elsNotOfVoice) > 0:
+                groups.insert(0, (elsNotOfVoice, None))
         else:  # create a single collection
             groups = [(srcObj, None)]
         # environLocal.printDebug(['offsetMap', groups])


Test patch:
diff --git a/music21/test/testStream.py b/music21/test/testStream.py
index b468682d0..9d6416a8a 100644
--- a/music21/test/testStream.py
+++ b/music21/test/testStream.py
@@ -3893,7 +3893,9 @@ class Test(unittest.TestCase):
         '''Testing voices making routines within make notation
         '''
         from music21 import stream
+        from music21.instrument import Xylophone
         s = stream.Stream()
+        s.insert(0, Xylophone())
         s.insert(0, note.Note('C4', quarterLength=8))
         s.repeatInsert(note.Note('b-4', quarterLength=0.5), [x * 0.5 for x in range(16)])
         s.repeatInsert(note.Note('f#5', quarterLength=2), [0, 2, 4, 6])
@@ -3912,6 +3914,9 @@ class Test(unittest.TestCase):
             for n in m.voices[1].notes:  # middle voice has beams
                 self.assertGreater(len(n.beams), 0)
 
+        # check instruments
+        self.assertIsInstance(sPost.getInstruments(recurse=True)[0], Xylophone)
+
     def testMakeNotationC(self):
         '''Test creating diverse, overlapping durations and notes
         '''