Repository: cuthbertLab/music21
Pull request #367 (2019-02-02 09:26:05)

Key signature for multi voice score being exported
When a score has multiple voices (i.e. two notes played at the same time and not combined into a Chord) then the KeySignature is lost when the score is run through 

```
        GEX = m21ToXml.GeneralObjectExporter(s)
        wellFormedObject = GEX.fromGeneralObject(s)
```

The bug presented as a missing key signature when I was using `score.show()` and looking at the output in MuseScore, or when I was exporting to `.musicxml` file.

My fix works, but it feels a tad hacky. I don't quite understand how or why the keySignature gets destroyed in a multi-voice situation. Code review is be appreciated!

I included tests-- one that started passing, and one that started broken.

mscuthbert: Ah -- this is entirely possible;  testing automatic voice creation during `makeNotation()` is not where it could be.  However, the bug is in `stream.makeNotation.makeMeasures()` or somewhere in that file -- the musicxml export, etc. is just a consumer of that function.  So the best thing to do is to move the tests closer to the problem, and test just the stream before and after makeNotation.  Also to test is that a stream that already has voices but not measures works properly.

Note the four failing tests after the change. 

Thanks for looking into this, and I would appreciate a patch.

Patch:
diff --git a/music21/analysis/neoRiemannian.py b/music21/analysis/neoRiemannian.py
index 247a42e54..49d42d9a9 100644
--- a/music21/analysis/neoRiemannian.py
+++ b/music21/analysis/neoRiemannian.py
@@ -7,12 +7,12 @@
 #               Michael Scott Cuthbert
 #               Mark Gotham
 #
-# Copyright:    Copyright © 2017-19 Michael Scott Cuthbert and the music21 Project
+# Copyright:    Copyright © 2017-18 Michael Scott Cuthbert and the music21 Project
 # License:      LGPL or BSD, see license.txt
 # ------------------------------------------------------------------------------
 '''
 This module defines the L, P, and R objects and their
-related transformations as called on a :class:`~music21.chord.Chord`,
+related transformations as called on a :class:`~music21.chord.Chord`, 
 according to Neo-Riemannian theory.
 '''
 import copy
@@ -35,17 +35,17 @@ class LRPException(exceptions21.Music21Exception):
 def _simplerEnharmonics(c):
     '''
     Returns a copy of chord `c` with pitches simplified.
-
+    
     Uses `:meth:music21.analysis.enharmonics.EnharmonicSimplifier.bestPitches`
-
+    
     >>> c = chord.Chord('B# F- G')
     >>> c2 = analysis.neoRiemannian._simplerEnharmonics(c)
     >>> c2
     <music21.chord.Chord C E G>
     >>> c3 = analysis.neoRiemannian._simplerEnharmonics(c2)
-
+    
     Returns a copy even if nothing has changed.
-
+    
     >>> c2 is c3
     False
     '''
@@ -58,7 +58,9 @@ def _simplerEnharmonics(c):
 
 def L(c, raiseException=True):
     '''
-    L transforms a major or minor triad through the 'Leading-Tone exchange'.
+    L is a function that takes a major or minor triad and returns a chord that
+    is the L transformation. L transforms a chord to its Leading-Tone exchange.
+
     A C major chord, under L, will return an E minor chord, by transforming the root
     to its leading tone, B.
 
@@ -72,7 +74,7 @@ def L(c, raiseException=True):
     >>> c3 = chord.Chord('C4 D4 E4')
     >>> analysis.neoRiemannian.L(c3)
     Traceback (most recent call last):
-    music21.analysis.neoRiemannian.LRPException: Cannot perform L on this chord:
+    music21.analysis.neoRiemannian.LRPException: Cannot perform L on this chord: 
         not a major or minor triad
 
     If `raiseException` is `False` then the original chord is returned.
@@ -80,15 +82,7 @@ def L(c, raiseException=True):
     >>> c4 = analysis.neoRiemannian.L(c3, raiseException=False)
     >>> c4 is c3
     True
-
-    Accepts any music21 chord, with or without octave specified:
-
-    >>> noOctaveChord = chord.Chord([0, 3, 7])
-    >>> chordL = analysis.neoRiemannian.L(noOctaveChord)
-    >>> chordL.pitches
-    (<music21.pitch.Pitch C>, <music21.pitch.Pitch E->, <music21.pitch.Pitch A->)
     '''
-
     if c.isMajorTriad():
         transposeInterval = '-m2'
         changingPitch = c.root()
@@ -100,15 +94,14 @@ def L(c, raiseException=True):
             raise LRPException('Cannot perform L on this chord: not a major or minor triad')
         return c
 
-    outChord = _singlePitchTransform(c, transposeInterval, changingPitch)
-    outChord.quarterLength = c.quarterLength
+    return _LRP_transform(c, transposeInterval, changingPitch)
 
-    return outChord
 
 def P(c, raiseException=True):
     '''
-    P transforms a major or minor triad chord to its 'parallel':
-    i.e. to the chord of the same diatonic name but opposite model.
+    P is a function that takes a major or minor triad and returns a chord that
+    is the P transformation. P transforms a chord to its parallel, i.e. to the
+    chord of the same diatonic name but opposite model.
 
     Example: A C major chord, under P, will return an C minor chord
 
@@ -120,7 +113,7 @@ def P(c, raiseException=True):
     See :func:`~music21.analysis.neoRiemannian.L` for further details about error handling.
 
     OMIT_FROM_DOCS
-
+    
     >>> c3 = chord.Chord('C4 D4 E4')
     >>> analysis.neoRiemannian.P(c3)
     Traceback (most recent call last):
@@ -136,15 +129,13 @@ def P(c, raiseException=True):
         if raiseException is True:
             raise LRPException('Cannot perform P on this chord: not a Major or Minor triad')
         return c
+    return _LRP_transform(c, transposeInterval, changingPitch)
 
-    outChord = _singlePitchTransform(c, transposeInterval, changingPitch)
-    outChord.quarterLength = c.quarterLength
-
-    return outChord
 
 def R(c, raiseException=True):
     '''
-    R transforms a major or minor triad to its relative, i.e. if
+    R is a function that takes a major or minor triad and returns a chord that
+    is the R transformation. R transforms a chord to its relative, i.e. if
     major, to its relative minor and if minor, to its relative major.
 
     Example 1: A C major chord, under R, will return an A minor chord
@@ -157,7 +148,7 @@ def R(c, raiseException=True):
     See :func:`~music21.analysis.neoRiemannian.L` for further details about error handling.
 
     OMIT_FROM_DOCS
-
+    
     >>> c3 = chord.Chord('C4 D4 E4')
     >>> analysis.neoRiemannian.R(c3)
     Traceback (most recent call last):
@@ -174,14 +165,11 @@ def R(c, raiseException=True):
             raise LRPException('Cannot perform R on this chord: not a Major or Minor triad')
         return c
 
-    outChord = _singlePitchTransform(c, transposeInterval, changingPitch)
-    outChord.quarterLength = c.quarterLength
-
-    return outChord
+    return _LRP_transform(c, transposeInterval, changingPitch)
 
-def _singlePitchTransform(c, transposeInterval, changingPitch):
+def _LRP_transform(c, transposeInterval, changingPitch):
     '''
-    Performs a neoRiemannian transformation on c that involves transposing `changingPitch` by
+    Performs a neoRiemannian transformation on c that involves transposing `changingPitch`    
     `transposeInterval`.
     '''
     changingPitchCopy = copy.deepcopy(changingPitch)
@@ -191,109 +179,17 @@ def _singlePitchTransform(c, transposeInterval, changingPitch):
             newChord.pitches[i].transpose(transposeInterval, inPlace=True)
     return chord.Chord(newChord.pitches)
 
-# ------------------------------------------------------------------------------
-
-def isNeoR(c1, c2, transforms='LRP'):
-    '''
-    Tests if two chords are related by a single L, P, R transformation,
-    and returns that transform if so (otherwise, False).
-
-    >>> c1 = chord.Chord('C4 E4 G4')
-    >>> c2 = chord.Chord('B3 E4 G4')
-    >>> analysis.neoRiemannian.isNeoR(c1, c2)
-    'L'
-
-    >>> c1 = chord.Chord('C4 E4 G4')
-    >>> c2 = chord.Chord('C4 E-4 G4')
-    >>> analysis.neoRiemannian.isNeoR(c1, c2)
-    'P'
-
-    >>> c1 = chord.Chord('C4 E4 G4')
-    >>> c2 = chord.Chord('C4 E4 A4')
-    >>> analysis.neoRiemannian.isNeoR(c1, c2)
-    'R'
-
-    >>> c1 = chord.Chord('C4 E4 G4')
-    >>> c2 = chord.Chord('C-4 E-4 A-4')
-    >>> analysis.neoRiemannian.isNeoR(c1, c2)
-    False
-
-    Option to limit to the search to only 1-2 transforms ('L', 'R', 'P', 'LR', 'LP', 'RP'). So:
-    >>> c3 = chord.Chord('C4 E-4 G4')
-    >>> analysis.neoRiemannian.isNeoR(c1, c3)
-    'P'
-
-    ... but not if P is excluded ...
-    >>> analysis.neoRiemannian.isNeoR(c1, c3, transforms='LR')
-    False
-    '''
-
-    c2NO = c2.normalOrder
-
-    transformList = [x for x in transforms]
-
-    for i in transformList:
-        if i == 'L':
-            c = L(c1)
-            if c.normalOrder == c2NO:
-                return 'L'
-                break
-        elif i == 'R':
-            c = R(c1)
-            if c.normalOrder == c2NO:
-                return 'R'
-                break
-        elif i == 'P':
-            c = P(c1)
-            if c.normalOrder == c2NO:
-                return 'P'
-                break
-        else:
-            raise LRPException('{} is not a NeoRiemannian transformation (L, R, or P)'.format(i))
-
-    return False # If neither an exception, nor any of the called L, R, or P tranforms
-
-def isChromaticMediant(c1, c2):
-    '''
-    Tests if there is a chromatic mediant relation between two chords
-    and returns the type if so (otherwise, False).
-
-    >>> c1 = chord.Chord('C4 E4 G4')
-    >>> c2 = chord.Chord('A-3 C4 E-4')
-    >>> analysis.neoRiemannian.isChromaticMediant(c1, c2)
-    'LFM'
-
-    >>> c3 = chord.Chord('C4 E4 G4')
-    >>> c4 = chord.Chord('C-4 E-4 A-4')
-    >>> analysis.neoRiemannian.isChromaticMediant(c3, c4)
-    False
-    '''
-
-    transformList = ['UFM', 'USM', 'LFM', 'LSM']
-
-    c2NO = c2.normalOrder
-
-    for transform in transformList:
-        thisChord = chromaticMediants(c1, transformation=transform)
-        if thisChord.normalOrder == c2NO:
-            return transform
-
-    return False
-
-# ------------------------------------------------------------------------------
-
 def LRP_combinations(c,
                      transformationString,
                      raiseException=True,
                      leftOrdered=False,
-                     simplifyEnharmonics=False,
-                     eachOne=False):
+                     simplifyEnharmonics=False):
     '''
     LRP_combinations takes a major or minor triad, tranforms it according to the
     list of L, R, and P transformations in the given transformationString, and
     returns the result in triad.
     Certain combinations, such as LPLPLP, are cyclical, and therefore
-    will return a copy of the original chord if `simplifyEnharmonics` = `True` (see
+    will return a copy of the original chord if `simplifyEnharmonics` = `True` (see 
     :func:`~music21.analysis.neoRiemannian.completeHexatonic`, below).
 
     `leftOrdered` allows a user to work with their preferred function notation (left or right
@@ -301,12 +197,12 @@ def LRP_combinations(c,
 
     By default, `leftOrdered` is False, so transformations progress towards the right.
     Thus 'LPR' will start by transforming the chord by L, then P, then R.
-
+    
     If `leftOrdered` is True, the operations work in the opposite direction (right to left), so
     'LPR' indicates the result of the chord transformed by R, then P, then L.
 
-    `simplifyEnharmonics` replaces multiple sharps and flats where they arise from
-    combined transformations with the simpler enharmonic equivalent (e.g. C for D--).
+    `simplifyEnharmonics` returns results removing multiple sharps and flats where
+    they arise from combined transformations.
     If simplifyEnharmonics is True, the resulting chord will be simplified
     to notes with at most 1 flat or 1 sharp, in their most common form.
 
@@ -319,13 +215,14 @@ def LRP_combinations(c,
     >>> c2b
     <music21.chord.Chord C4 E-4 A-4>
 
+
     >>> c3 = chord.Chord('C4 E4 G4 C5 E5')
     >>> c4 = analysis.neoRiemannian.LRP_combinations(c3, 'RLP')
     >>> c4
     <music21.chord.Chord C4 F4 A-4 C5 F5>
 
     >>> c5 = chord.Chord('B4 D#5 F#5')
-    >>> c6 = analysis.neoRiemannian.LRP_combinations(c5,
+    >>> c6 = analysis.neoRiemannian.LRP_combinations(c5, 
     ...                        'LPLPLP', leftOrdered=True, simplifyEnharmonics=True)
     >>> c6
     <music21.chord.Chord B4 D#5 F#5>
@@ -337,59 +234,32 @@ def LRP_combinations(c,
     >>> c6 = analysis.neoRiemannian.LRP_combinations(c5, 'LPLPLP')
     >>> c6
     <music21.chord.Chord G#4 B#3 D#5>
-
-    Optionally: return all of the chords creating by the given string in order.
-    >>> c7 = chord.Chord('C4 E4 G4')
-    >>> c8 = analysis.neoRiemannian.LRP_combinations(c7, 'LPLPLP', simplifyEnharmonics=True, eachOne=True)
-    >>> c8
-    [<music21.chord.Chord B3 E4 G4>,
-    <music21.chord.Chord B3 E4 G#4>,
-    <music21.chord.Chord B3 D#4 G#4>,
-    <music21.chord.Chord C4 E-4 A-4>,
-    <music21.chord.Chord C4 E-4 G4>,
-    <music21.chord.Chord C4 E4 G4>]
-
-    On that particular case of LPLPLP, see more in
-    :func:`~music21.analysis.neoRiemannian.completeHexatonic`.
     '''
-
-    if c.forteClassTnI != '3-11': # First to avoid doing anything else if fail
+    if leftOrdered:
+        transformationString = transformationString[::-1]
+    if c.forteClassTnI != '3-11':
         if raiseException is True:
             raise LRPException(
                 'Cannot perform transformations on chord {}: not a major or minor triad'.format(c))
         return c
 
-    if leftOrdered:
-        transformationString = transformationString[::-1]
-
-    chordList = []
-
     for i in transformationString:
         if i == 'L':
             c = L(c)
-            if eachOne == True:
-                chordList.append(copy.deepcopy(c))
         elif i == 'R':
             c = R(c)
-            if eachOne == True:
-                chordList.append(copy.deepcopy(c))
         elif i == 'P':
             c = P(c)
-            if eachOne == True:
-                chordList.append(copy.deepcopy(c))
         else:
             raise LRPException('{} is not a NeoRiemannian transformation (L, R, or P)'.format(i))
 
-    if eachOne == True:
-        if simplifyEnharmonics is False:
-            return chordList
-        else:
-            return [_simplerEnharmonics(x) for x in chordList]
+    if simplifyEnharmonics is False:
+        return c
     else:
-        if simplifyEnharmonics is False:
-            return c
-        else:
-            return _simplerEnharmonics(c)
+        return _simplerEnharmonics(c)
+        ## Previously:
+        # newPitches = pitch.simplifyMultipleEnharmonics(c.pitches, keyContext=key.Key('C'))
+
 
 def completeHexatonic(c, simplifyEnharmonics=False, raiseException=True):
     '''
@@ -397,7 +267,7 @@ def completeHexatonic(c, simplifyEnharmonics=False, raiseException=True):
     This six-part operation cycles between major and minor triads, ultimately returning to the
     input triad (or its enharmonic equivalent).
     This functions returns those six triads, ending with the original triad.
-
+    
     `simplifyEnharmonics` is False, by default, giving double flats at the end.
 
     >>> c1 = chord.Chord('C4 E4 G4')
@@ -410,7 +280,7 @@ def completeHexatonic(c, simplifyEnharmonics=False, raiseException=True):
      <music21.chord.Chord D--4 F-4 A--4>]
 
     `simplifyEnharmonics` can be set to True in order to avoid this.
-
+     
     >>> c2 = chord.Chord('C4 E4 G4')
     >>> analysis.neoRiemannian.completeHexatonic(c2, simplifyEnharmonics=True)
     [<music21.chord.Chord C4 E-4 G4>,
@@ -443,7 +313,7 @@ def hexatonicSystem(c):
     Hexatonic Systems, and the Analysis of Late-Romantic
     Triadic Progressions," *Music Analysis*, 15.1 (1996), 9-40,
     at p. 17.  Possible values are 'northern', 'western', 'eastern', or 'southern'
-
+    
     >>> cMaj = chord.Chord('C E G')
     >>> analysis.neoRiemannian.hexatonicSystem(cMaj)
     'northern'
@@ -451,10 +321,10 @@ def hexatonicSystem(c):
     >>> gMin = chord.Chord('G B- D')
     >>> analysis.neoRiemannian.hexatonicSystem(gMin)
     'western'
-
-    Each chord in the :func:`~music21.analysis.neoRiemannian.completeHexatonic` of that chord
+    
+    Each chord in the :func:`~music21.analysis.neoRiemannian.completeHexatonic` of that chord 
     is in the same hexatonic system, by definition or tautology.
-
+    
     >>> for ch in analysis.neoRiemannian.completeHexatonic(cMaj):
     ...     print(analysis.neoRiemannian.hexatonicSystem(ch))
     northern
@@ -463,11 +333,11 @@ def hexatonicSystem(c):
     northern
     northern
     northern
-
+        
     Note that the classification looks only at the
     pitch class of the root of the chord.  Seventh chords,
     diminished triads, etc. will also be classified.
-
+    
     >>> dDom65 = chord.Chord('F#4 D5 A5 C6')
     >>> analysis.neoRiemannian.hexatonicSystem(dDom65)
     'southern'
@@ -478,7 +348,7 @@ def hexatonicSystem(c):
     mappings = [({0, 4, 8}, 'northern'),
                 ({1, 5, 9}, 'eastern'),
                 ({2, 6, 10}, 'southern'),
-                ({3, 7, 11}, 'western'),
+                ({3, 7, 11}, 'western'),                
                 ]
     for pcSet, poleName in mappings:
         if rootPC in pcSet:
@@ -486,156 +356,10 @@ def hexatonicSystem(c):
 
     # pragma: no-cover
     raise LRPException('Odd pitch class that is not in 0 to 11!')
+    
+    
 
 # ------------------------------------------------------------------------------
-
-# Some special of LPR combinations
-
-def chromaticMediants(c, transformation='UFM'):
-    '''
-    Transforms a chord into the given chromatic mediant. Options:
-    UFM = Upper Flat Mediant;
-    USM = Upper Sharp Mediant;
-    LFM = Lower Flat Mediant ;
-    LSM = Lower Sharp Mediant;
-
-    Each of these transformations
-    is mode-preserving;
-    involves root motion by a third;
-    entails exactly one common-tone.
-
-    >>> cMaj = chord.Chord('C5 E5 G5')
-    >>> UFMcMaj = analysis.neoRiemannian.chromaticMediants(cMaj, transformation='UFM')
-    >>> UFMcMaj.normalOrder
-    [3, 7, 10]
-
-    >>> USMcMaj = analysis.neoRiemannian.chromaticMediants(cMaj, transformation='USM')
-    >>> USMcMaj.normalOrder
-    [4, 8, 11]
-
-    >>> LFMcMaj = analysis.neoRiemannian.chromaticMediants(cMaj, transformation='LFM')
-    >>> [x.nameWithOctave for x in LFMcMaj.pitches]
-    ['C5', 'E-5', 'A-5']
-
-    >>> LSMcMaj = analysis.neoRiemannian.chromaticMediants(cMaj, transformation='LSM')
-    >>> [x.nameWithOctave for x in LSMcMaj.pitches]
-    ['C#5', 'E5', 'A5']
-
-    Fine to call on a chord with multiple enharmonics,
-    but will always simplify the output chromatic mediant.
-
-    >>> cMajEnh = chord.Chord('D--5 F-5 A--5')
-    >>> USM = analysis.neoRiemannian.chromaticMediants(cMaj, transformation='USM')
-    >>> [x.nameWithOctave for x in USM.pitches]
-    ['B4', 'E5', 'G#5']
-    >>> USM.normalOrder == USMcMaj.normalOrder
-    True
-    '''
-
-    options = ['UFM', 'USM', 'LFM', 'LSM']
-    if transformation not in options:
-        raise ValueError('Transformation must be one of %s' %options)
-
-    transformationString = 'PR' # Initialised for 'UFM'
-    if transformation == 'USM':
-        transformationString = 'LP'
-    elif transformation == 'LFM':
-        transformationString = 'PL'
-    elif transformation == 'LSM':
-        transformationString = 'RP'
-
-    if c.isMinorTriad():
-        LO =True
-    elif c.isMajorTriad():
-        LO = False
-    else:
-        raise ValueError('Chord must be major or minor')
-
-    c = LRP_combinations(c, transformationString, leftOrdered=LO)
-
-    return _simplerEnharmonics(c)
-
-def disjunctMediants(c, upperOrLower='upper'):
-    '''
-    Transforms a chord into the upper or lower disjunct mediant.
-    These transformations involve:
-    root motion by a non-diatonic third;
-    mode-change;
-    no common-tones.
-
-    >>> cMaj = chord.Chord('C5 E5 G5')
-    >>> upr = analysis.neoRiemannian.disjunctMediants(cMaj, upperOrLower='upper')
-    >>> [x.name for x in upr.pitches]
-    ['B-', 'E-', 'G-']
-    >>> lwr = analysis.neoRiemannian.disjunctMediants(cMaj, upperOrLower='lower')
-    >>> [x.name for x in lwr.pitches]
-    ['B', 'D#', 'G#']
-    '''
-
-    options = ['upper', 'lower']
-    if upperOrLower not in options:
-        raise ValueError('upperOrLower must be one of %s' %options)
-
-    transformationString = 'PRP' # Initialised for major upper and minor lower
-
-    if c.isMajorTriad():
-        if upperOrLower == 'lower': # Change
-            transformationString = 'PLP'
-    elif c.isMinorTriad():
-        if upperOrLower == 'upper':
-            transformationString = 'PLP'
-    else:
-        raise ValueError('Chord must be major or minor')
-
-    c = LRP_combinations(c, transformationString)
-
-    return _simplerEnharmonics(c)
-
-def S(c):
-    '''
-    Slide transform connecting major and minor triads with the third as the single common tone
-    (i.e. with the major triad root a semi-tone below the minor). So:
-    root motion by a semi-tone;
-    mode-change;
-    one common-tones.
-    Slide is equivalent to 'LPR'.
-
-    >>> cMaj = chord.Chord('C5 E5 G5')
-    >>> slideUp = analysis.neoRiemannian.S(cMaj)
-    >>> [x.name for x in slideUp.pitches]
-    ['C#', 'E', 'G#']
-
-    >>> aMin = chord.Chord('A4 C5 E5')
-    >>> slideDown = analysis.neoRiemannian.S(aMin)
-    >>> [x.name for x in slideDown.pitches]
-    ['A-', 'C', 'E-']
-    '''
-
-    return LRP_combinations(c, 'LPR', simplifyEnharmonics=False)
-
-def N(c):
-    '''
-    The 'Neberverwandt' ('fifth-change') transform connects
-    a minor triad with its major dominant, and so also
-    a major triad with its minor sub-dominant,
-    with one common tone in each case.
-    This is equivalent to 'RLP'.
-
-    >>> cMaj = chord.Chord('C5 E5 G5')
-    >>> n1 = analysis.neoRiemannian.N(cMaj)
-    >>> [x.name for x in n1.pitches]
-    ['C', 'F', 'A-']
-
-    >>> aMin = chord.Chord('A4 C5 E5')
-    >>> n2 = analysis.neoRiemannian.N(aMin)
-    >>> [x.name for x in n2.pitches]
-    ['G#', 'B', 'E']
-    '''
-
-    return LRP_combinations(c, 'RLP', simplifyEnharmonics=False)
-
-# ------------------------------------------------------------------------------
-
 class Test(unittest.TestCase):
 
     def testNeoRiemannianTransformations(self):
@@ -677,91 +401,8 @@ class Test(unittest.TestCase):
         c7_T = LRP_combinations(c7, 'LP', leftOrdered=True)
         self.assertEqual(str(c7_T), '<music21.chord.Chord C4 E-4 A-4 C5 E-5>')
 
-        c8 = chord.Chord('C-4 E-4 G-4')
-        c8_T = LRP_combinations(c8, 'LP')
-        self.assertEqual(str(c8_T), '<music21.chord.Chord B-3 E-4 G4>')
-
-    def testIsNeoR(self):
-
-        c1 = chord.Chord('C4 E4 G4')
-
-        c2 = chord.Chord('B3 E4 G4')
-        ans1 = isNeoR(c1, c2)
-        self.assertEqual(ans1, 'L')
-
-        c3 = chord.Chord('C4 E-4 G4')
-        ans2 = isNeoR(c1, c3)
-        self.assertEqual(ans2, 'P')
-        # ... But not if P is excluded ...
-        ans2 = isNeoR(c1, c3, transforms='LR')
-        self.assertEqual(ans2, False)
-
-        c4 = chord.Chord('C4 E4 A4')
-        ans3 = isNeoR(c1, c4)
-        self.assertEqual(ans3, 'R')
-
-        c5 = chord.Chord('C4 E-4 A-4')
-        ans4 = isChromaticMediant(c1, c5)
-        self.assertEqual(ans4, 'LFM')
-
-        c6 = chord.Chord('C-4 E-4 A-4')
-        ans5 = isNeoR(c1, c6)
-        ans6 = isChromaticMediant(c1, c6)
-        self.assertEqual(ans5, False)
-        self.assertEqual(ans6, False) # disjunct mediants not currently included
-
-        c7 = chord.Chord('C-4 E-4 G-4')
-        c8 = chord.Chord('C-4 E--4 A--4')
-        ans7 = isNeoR(c7, c8)
-        ans8 = isChromaticMediant(c7, c8)
-        self.assertEqual(ans7, False)
-        self.assertEqual(ans8, 'LFM')
-
-    def testMediants(self):
-
-        c9 = chord.Chord('C5 E5 G5')
-        c9a = chord.Chord('C#5 E#5 G#5')
-
-        UFMcMaj = chromaticMediants(c9, transformation='UFM')
-        self.assertEqual(UFMcMaj.normalOrder, [3, 7, 10])
-
-        USMcMaj = chromaticMediants(c9, transformation='USM')
-        self.assertEqual(USMcMaj.normalOrder, [4, 8, 11])
-
-        LFMcMaj = chromaticMediants(c9, transformation='LFM')
-        self.assertEqual([x.nameWithOctave for x in LFMcMaj.pitches], ['C5', 'E-5', 'A-5'])
-
-        LSMcMaj = chromaticMediants(c9, transformation='LSM')
-        self.assertEqual([x.nameWithOctave for x in LSMcMaj.pitches], ['C#5', 'E5', 'A5'])
-
-        upChrom = disjunctMediants(c9a, upperOrLower='upper')
-        self.assertEqual([x.name for x in upChrom.pitches], ['B', 'E', 'G'])
-
-        downChrom = disjunctMediants(c9a, upperOrLower='lower')
-        self.assertEqual([x.nameWithOctave for x in downChrom.pitches], ['C5', 'E5', 'A5'])
-
-    def testSnN(self):
-
-        c10 = chord.Chord('C5 E5 G5')
-        c11 = chord.Chord('A4 C5 E5')
-
-        slideUp = S(c10)
-        self.assertEqual([x.name for x in slideUp.pitches], ['C#', 'E', 'G#'])
-
-        slideDown = S(c11)
-        self.assertEqual([x.name for x in slideDown.pitches], ['A-', 'C', 'E-'])
-
-        N1 = N(c10)
-        self.assertEqual([x.name for x in N1.pitches], ['C', 'F', 'A-'])
-
-        N2 = N(c11)
-        self.assertEqual([x.name for x in N2.pitches], ['G#', 'B', 'E'])
-
 # ------------------------------------------------------------------------------
-_DOC_ORDER = [L, R, P, S, N, isNeoR,
-                LRP_combinations, completeHexatonic, hexatonicSystem, LRPException,
-                chromaticMediants, isChromaticMediant,
-                disjunctMediants,]
+_DOC_ORDER = [L, R, P, LRP_combinations, completeHexatonic, hexatonicSystem, LRPException]
 
 # ------------------------------------------------------------------------------
 if __name__ == '__main__':
diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py
index 020a807ac..93d59ad2a 100644
--- a/music21/stream/makeNotation.py
+++ b/music21/stream/makeNotation.py
@@ -497,6 +497,8 @@ def makeMeasures(
         # creating Measures; this clef is from getClefs, called above
         if measureCount == 0:
             m.clef = clefObj
+            if voiceCount > 0 and s.keySignature is not None:
+                m.insert(0, s.keySignature)
             # environLocal.printDebug(
             #    ['assigned clef to measure', measureCount, m.clef])
 


Test patch:
diff --git a/music21/test/testStream.py b/music21/test/testStream.py
index 0964f55e4..7941703b5 100644
--- a/music21/test/testStream.py
+++ b/music21/test/testStream.py
@@ -3905,6 +3905,55 @@ class Test(unittest.TestCase):
         self.assertEqual(len(post.flat.getElementsByClass('Clef')), 2)
 
 
+    def testMakeNotationKeySignatureOneVoice(self):
+        '''
+        The base-case: Stream should keep it's key.KeySignature element when a single-voice score is prepared for notation.
+        '''
+        sharpsInKey = 2
+        c = clef.TrebleClef()
+        ts = meter.TimeSignature('4/4')
+        ks = key.KeySignature(sharpsInKey)
+        s = Stream()
+        s.insert(0.0, c)
+        s.insert(0.0, ts)
+        s.insert(0.0, ks)
+        s.insert(0.0, note.Note('C'))
+        s.insert(2.0, note.Note('G'))
+
+        sPost = s.makeNotation()
+
+        self.assertEqual(len(sPost.getElementsByClass('Measure')), 1)
+        m1 = sPost.getElementsByClass('Measure')[0]
+        self.assertEqual(m1.keySignature.sharps, sharpsInKey)
+
+    def testMakeNotationKeySignatureMultiVoice(self):
+        '''
+        Stream should keep it's key.KeySignature element when a multi-voice score is prepared for notation.
+        '''
+
+        sharpsInKey = 2
+        c = clef.TrebleClef()
+        ts = meter.TimeSignature('4/4')
+        ks = key.KeySignature(2)
+        s = Stream()
+        s.insert(0.0, c)
+        s.insert(0.0, ts)
+        s.insert(0.0, ks)
+
+        # two notes at the same time create multi-voice
+        s.insert(2.0, note.Note('C'))
+        s.insert(2.0, note.Note('G'))
+
+        sPost = s.makeNotation()
+
+        # self.assertEqual(post.hasPartLikeStreams(), True)
+        # print("sPost.getElementsByClass('Measure')", sPost.getElementsByClass('Measure'))
+        self.assertEqual(len(sPost.getElementsByClass('Measure')), 1)
+        m1 = sPost.getElementsByClass('Measure')[0]
+        # print("sPost.getElementsByClass('Measure')[0]", m1)
+        # print('m1.keySignature',m1.keySignature.sharps)
+        assert(m1.keySignature is not None)
+        self.assertEqual(m1.keySignature.sharps, sharpsInKey)
 
     def testMakeTies(self):