Repository: cuthbertLab/music21
Pull request #389 (2019-05-06 07:15:20)

abc accidental propagation, three flavors (for notes anyway)
I have implemented and tested code to support the three `@@propagate-accidentals` alternatives in the latest (2.1) abc standard, but only for files explicitly identified as `@abc-2.1` files or for callers requesting this type of parsing.

I have not done anything to change how ties are processed. This is left for future discussion/work.

Chords are another topic for investigation.


Patch:
diff --git a/music21/abcFormat/__init__.py b/music21/abcFormat/__init__.py
index 98ad48cec..b2158cd88 100644
--- a/music21/abcFormat/__init__.py
+++ b/music21/abcFormat/__init__.py
@@ -91,6 +91,8 @@ reMetadataTag = re.compile('[A-Zw]:')
 rePitchName = re.compile('[a-gA-Gz]')
 reChordSymbol = re.compile('"[^"]*"') # non greedy
 reChord = re.compile('[.*?]') # non greedy
+reAbcVersion = re.compile(r'^%abc-((\d+)\.(\d+)\.?(\d+)?)')
+reDirective = re.compile(r'^%%([a-z\-]+)\s+([^\s]+)(.*)')
 
 
 # ------------------------------------------------------------------------------
@@ -103,7 +105,7 @@ class ABCHandlerException(exceptions21.Music21Exception):
 
 class ABCFileException(exceptions21.Music21Exception):
     pass
-
+#
 
 
 # ------------------------------------------------------------------------------
@@ -713,7 +715,6 @@ class ABCMetadata(ABCToken):
                 'no quarter length associated with this meta-data: %s' % self.data)
 
 
-
 class ABCBar(ABCToken):
 
     # given a logical unit, create an object
@@ -1216,7 +1217,6 @@ class ABCBrokenRhythmMarker(ABCToken):
         self.data = self.src.strip()
 
 
-
 class ABCNote(ABCToken):
     '''
     A model of an ABCNote.
@@ -1235,11 +1235,18 @@ class ABCNote(ABCToken):
     # given a logical unit, create an object
     # may be a chord, notes, bars
 
-    def __init__(self, src=''):
+    def __init__(self, src='', carriedAccidental=None):
         super().__init__(src)
+        
+        # store the ABC accidental string propagated in the measure that
+        # must be applied to this note. Note must not be set if the
+        # note already has an explicit accidental attached. (The explicit
+        # accidental is now the one that will be carried forward.)
+        self.carriedAccidental = carriedAccidental
+        
         # store chord string if connected to this note
         self.chordSymbols = []
-
+       
         # context attributes
         self.inBar = None
         self.inBeam = None
@@ -1275,11 +1282,9 @@ class ABCNote(ABCToken):
         self.pitchName = None # if None, a rest or chord
         self.quarterLength = None
 
-
     def __repr__(self):
         return '<music21.abcFormat.ABCNote %r>' % self.src
 
-
     @staticmethod
     def _splitChordSymbols(strSrc):
         '''
@@ -1307,7 +1312,6 @@ class ABCNote(ABCToken):
         else:
             return [], strSrc
 
-
     def getPitchName(self, strSrc, forceKeySignature=None):
         '''
         Given a note or rest string without a chord symbol,
@@ -1376,7 +1380,7 @@ class ABCNote(ABCToken):
             activeKeySignature = self.activeKeySignature
 
         try: # returns pStr, accidentalDisplayStatus
-            return _pitchTranslationCache[(strSrc, str(activeKeySignature))]
+            return _pitchTranslationCache[(strSrc, self.carriedAccidental, str(activeKeySignature))]
         except KeyError:
             pass
 
@@ -1389,17 +1393,35 @@ class ABCNote(ABCToken):
         octave += strSrc.count("'")
 
         # get an accidental string
+        
         accString = ''
-        for i in range(strSrc.count('_')):
+        for dummy in range(strSrc.count('_')):
             accString += '-' # m21 symbols
-        for i in range(strSrc.count('^')):
+        for dummy in range(strSrc.count('^')):
             accString += '#' # m21 symbols
-        for i in range(strSrc.count('=')):
+        for dummy in range(strSrc.count('=')):
             accString += 'n' # m21 symbols
-
+        
+        carriedAccString = ''
+        if self.carriedAccidental:
+            # No overriding accidental attached to this note
+            # force carrying through the measure.
+            for dummy in range(self.carriedAccidental.count('_')):
+                carriedAccString += '-' # m21 symbols
+            for dummy in range(self.carriedAccidental.count('^')):
+                carriedAccString += '#' # m21 symbols
+            for dummy in range(self.carriedAccidental.count('=')):
+                carriedAccString += 'n' # m21 symbols
+
+        if carriedAccString and accString:
+            raise ABCHandlerException("Carried accidentals not rendered moot.")
         # if there is an explicit accidental, regardless of key, it should
         # be shown: this will works for naturals well
-        if accString != '':
+        if carriedAccString:
+            # An accidental carrying through the measure is supposed to be applied.
+            # This will be set iff no explicit accidental is attached to the note.
+            accidentalDisplayStatus = None
+        elif accString != '':
             accidentalDisplayStatus = True
         # if we do not have a key signature, and have accidentals, set to None
         elif activeKeySignature is None:
@@ -1422,13 +1444,16 @@ class ABCNote(ABCToken):
             accidentalDisplayStatus = False
 
         # making upper here, but this is not relevant
-        pStr = '%s%s%s' % (name.upper(), accString, octave)
+        if carriedAccString:
+            pStr = '%s%s%s' % (name.upper(), carriedAccString, octave)
+        else:
+            pStr = '%s%s%s' % (name.upper(), accString, octave)
 
         # store in global cache
-        _pitchTranslationCache[(strSrc, str(activeKeySignature))] = pStr, accidentalDisplayStatus
+        _pitchTranslationCache[(strSrc, self.carriedAccidental, str(activeKeySignature))] = \
+            pStr, accidentalDisplayStatus
         return pStr, accidentalDisplayStatus
 
-
     def getQuarterLength(self, strSrc, forceDefaultQuarterLength=None):
         '''
         Called with parse(), after context processing, to calculate duration
@@ -1541,10 +1566,8 @@ class ABCNote(ABCToken):
             elif direction == 'right':
                 ql *= modPair[1]
 
-
         return ql
 
-
     def parse(self, forceDefaultQuarterLength=None,
                     forceKeySignature=None):
         # environLocal.printDebug(['parse', self.src])
@@ -1639,16 +1662,18 @@ class ABCChord(ABCNote):
 
 # ------------------------------------------------------------------------------
 class ABCHandler:
-
     # divide elements of a character stream into objects and handle
-    # store in a list, and pass global information to compontns
-    def __init__(self):
-        # tokens are ABC objects in a linear stream
+    # store in a list, and pass global information to components
+    # Optionally, specify the (major, minor, patch) version of ABC to process--
+    # e.g., (1.2.0). If not set, default ABC 1.3 parsing is performed.
+    def __init__(self, abcVersion=None):
+        # tokens are ABC objects import n a linear stream
+        self.abcVersion = abcVersion
+        self.abcDirectives = {}
         self._tokens = []
         self.activeParens = []
         self.activeSpanners = []
 
-
     @staticmethod
     def _getLinearContext(strSrc, i):
         '''
@@ -1766,11 +1791,29 @@ class ABCHandler:
         else: # append unaltered
             post.append(ABCBar(token))
         return post
-
-
+    
     # --------------------------------------------------------------------------
     # token processing
 
+    def _accidentalPropagation(self):
+        '''
+        Determine how accidentals should "carry through the measure."
+        
+        >>> ah = abcFormat.ABCHandler(abcVersion=(1,3,0))
+        >>> ah._accidentalPropagation()
+        'not'
+        >>> ah = abcFormat.ABCHandler(abcVersion=(2,0,0))
+        >>> ah._accidentalPropagation()
+        'pitch'
+        '''
+        minVersion = (2,0,0)
+        if not self.abcVersion or self.abcVersion < minVersion:
+            return 'not'
+        if 'propagate-accidentals' in self.abcDirectives:
+            return self.abcDirectives['propagate-accidentals']
+        return 'pitch' # Default per abc 2.1 standard
+
+
     def tokenize(self, strSrc):
         '''
         Walk the abc string, creating ABC objects along the way.
@@ -1811,9 +1854,15 @@ class ABCHandler:
         collect = []
         lastIndex = len(strSrc) - 1
         skipAhead = 0
+        accidentalsAndDecorations = '.~^=_HLMOPSTuv'
+        accidentals = '^=_'
 
         activeChordSymbol = '' # accumulate, then prepend
-
+        accidentalized = {}
+        accidental = None
+        abcPitch = None # ABC substring defining any pitch within the current token
+        isFirstComment = True
+        
         while currentIndex < lastIndex:
             currentIndex += 1
             currentIndex += skipAhead
@@ -1828,6 +1877,24 @@ class ABCHandler:
             # comment lines, also encoding defs
             if c == '%':
                 skipAhead = self._getNextLineBreak(strSrc, currentIndex) - (currentIndex + 1)
+                commentLine = strSrc[currentIndex:currentIndex + skipAhead + 1]
+                if isFirstComment: 
+                    isFirstComment = False
+                    verMats = reAbcVersion.match(commentLine)
+                    if verMats:
+                        abcMajor = int(verMats.group(2))
+                        abcMinor = int(verMats.group(3))
+                        if verMats.group(4):
+                            abcPatch = int(verMats(4))
+                        else:
+                            abcPatch = 0
+                        verTuple = (abcMajor, abcMinor, abcPatch)
+                        self.abcVersion = verTuple
+                dirMats = reDirective.match(commentLine)
+                if dirMats:
+                    var = dirMats.group(1)
+                    val = dirMats.group(2)
+                    self.abcDirectives[var] = val
                 # environLocal.printDebug(['got comment:', repr(strSrc[i:j + 1])])
                 continue
 
@@ -1854,8 +1921,7 @@ class ABCHandler:
                 continue
 
             # get bars: if not a space and not alphanemeric
-            if (not c.isspace() and not c.isalnum()
-                and c not in ['~', '(']):
+            if (not c.isspace() and not c.isalnum() and c not in ['~', '(']):
                 matchBars = False
                 for barIndex in range(len(ABC_BARS)):
                     # first of bars tuple is symbol to match
@@ -1877,6 +1943,8 @@ class ABCHandler:
                             matchBars = True
                             break
                 if matchBars is True:
+                    accidentalized = {}
+                    accidental = None
                     j = currentIndex + skipAhead + 1
                     collect = strSrc[currentIndex:j]
                     # filter and replace with 2 tokens if necessary
@@ -1951,8 +2019,6 @@ class ABCHandler:
                 # not found, continue...
                 continue
 
-
-
             # get slurs, ensuring that they're not confused for tuplets
             if (c == '(' and cNext is not None and not cNext.isdigit()):
                 self._tokens.append(ABCSlurStart(c))
@@ -1968,7 +2034,6 @@ class ABCHandler:
                 self._tokens.append(ABCTie(c))
                 continue
 
-
             # get chord symbols / guitar chords; collected and joined with
             # chord or notes
             if (c == '"'):
@@ -1998,6 +2063,9 @@ class ABCHandler:
                 # environLocal.printDebug(['got chord:', repr(collect)])
                 self._tokens.append(ABCChord(collect))
                 skipAhead = j - (currentIndex + 1)
+                # TODO: Chords need to be aware of accidentals too.
+                # Also what happens to prefixes and suffixes attached to chords,
+                # like ties.
                 continue
 
             if (c == '.'):
@@ -2056,8 +2124,11 @@ class ABCHandler:
                 #     =       natural
                 #     _       flat
                 #     __      double-flat
-                accidentalsAndDecorations = '.~^=_HLMOPSTuv'
                 foundPitchAlpha = c.isalpha() and c not in accidentalsAndDecorations
+                if foundPitchAlpha:
+                    abcPitch = c
+                if c in accidentals:
+                    accidental = c
                 j = currentIndex + 1
 
                 while j <= lastIndex:
@@ -2065,17 +2136,22 @@ class ABCHandler:
                     # decorations and/or accidentals may precede note names
                     if (not foundPitchAlpha and strSrc[j] in accidentalsAndDecorations):
                         j += 1
+                        if strSrc[j] in accidentals:
+                            accidental += strSrc[j]
                         continue
                     # only allow one pitch alpha to be a continue condition
                     elif (not foundPitchAlpha and strSrc[j].isalpha()
                         and strSrc[j] not in '~wuvhHLTSN'):
                         foundPitchAlpha = True
+                        abcPitch = strSrc[j] 
                         j += 1
                         continue
                     # continue conditions after alpha:
                     # , register modification (, ') or number, rhythm indication
                     # number, /,
                     elif strSrc[j].isdigit() or strSrc[j] in ',/,\'':
+                        if strSrc[j] in ',\'':  # Register (octave) modification
+                            abcPitch += strSrc[j] 
                         j += 1
                         continue
                     else: # space, all else: break
@@ -2115,8 +2191,26 @@ class ABCHandler:
                 elif len(collect) > 1 and collect.startswith('=') and collect[1].isdigit():
                     pass
                 # only let valid collect strings be parsed
+                elif abcPitch:
+                    pitchClass = abcPitch[0].upper()
+                    carriedAccidental = None
+                    propagation = self._accidentalPropagation()
+                    if accidental:
+                        # Remember the active accidentals in the measure
+                        if propagation == 'octave':
+                            accidentalized[abcPitch] = accidental
+                        elif propagation == 'pitch':
+                            accidentalized[pitchClass] = accidental
+                        accidental = None
+                    else:
+                        if propagation == 'pitch' and pitchClass in accidentalized:
+                            carriedAccidental = accidentalized[pitchClass]
+                        elif propagation == 'octave' and abcPitch in accidentalized:
+                            carriedAccidental = accidentalized[abcPitch]
+                    self._tokens.append(ABCNote(collect, carriedAccidental=carriedAccidental))
                 else:
                     self._tokens.append(ABCNote(collect))
+                    
                 skipAhead = j - (currentIndex + 1)
                 continue
             # look for white space: can be used to determine beam groups
@@ -2154,7 +2248,6 @@ class ABCHandler:
         lastGraceToken = None
         lastNoteToken = None
 
-
         for i in range(len(self._tokens)):
             # get context of tokens
             q = self._getLinearContext(self._tokens, i)
@@ -2212,7 +2305,6 @@ class ABCHandler:
                     if p in ('Slur', 'Crescendo', 'Diminuendo'):
                         self.activeSpanners.pop()
 
-
             if isinstance(t, ABCTie):
                 # tPrev is usually an ABCNote but may be a GraceStop.
                 if lastNoteToken and lastNoteToken.tie == 'stop':
@@ -2239,8 +2331,6 @@ class ABCHandler:
             if isinstance(t, ABCTenuto):
                 lastTenutoToken = t
 
-
-
             if isinstance(t, ABCCrescStart):
                 t.fillCresc()
                 self.activeSpanners.append(t.crescObj)
@@ -2257,8 +2347,6 @@ class ABCHandler:
             if isinstance(t, ABCGraceStop):
                 lastGraceToken = None
 
-
-
             # ABCChord inherits ABCNote, thus getting note is enough for both
             if isinstance(t, (ABCNote, ABCChord)):
                 if lastDefaultQL is None:
@@ -2697,13 +2785,12 @@ class ABCHandler:
 
         Returns a list of ABCHandlerBar instances.
         The first usually defines only Metadata
-
+        
         TODO: Test and examples
         '''
         if self._tokens == []:
             raise ABCHandlerException('must process tokens before calling split')
 
-
         post = []
         barIndices = self.tokensToBarIndices()
 
@@ -2825,8 +2912,6 @@ class ABCHandler:
 
         return barIndices
 
-
-
     def hasNotes(self):
         '''
         If tokens are processed, return True if ABCNote or
@@ -2872,7 +2957,6 @@ class ABCHandler:
         return None
 
 
-
 class ABCHandlerBar(ABCHandler):
     '''
     A Handler specialized for storing bars. All left
@@ -2960,8 +3044,11 @@ def mergeLeadingMetaData(barHandlers):
 class ABCFile:
     '''
     ABC File or String access
+    # Optionally, specify the (major, minor, patch) version of ABC to process--
+    # e.g., (1.2.0). If not set, default ABC 1.3 parsing is performed.
     '''
-    def __init__(self):
+    def __init__(self, abcVersion=None):
+        self.abcVersion = abcVersion
         self.file = None
         self.filename = None
 
@@ -3049,7 +3136,7 @@ class ABCFile:
             # will raise exception if cannot be found
             strSrc = self.extractReferenceNumber(strSrc, number)
 
-        handler = ABCHandler()
+        handler = ABCHandler(abcVersion=self.abcVersion)
         # return the handler instance
         handler.process(strSrc)
         return handler
@@ -3126,7 +3213,6 @@ class Test(unittest.TestCase):
         self.assertEqual(rePitchName.findall(src)[0], 'A')
 
 
-
     def testTokenProcessMetadata(self):
         from music21.abcFormat import testFiles
 
@@ -3153,8 +3239,6 @@ class Test(unittest.TestCase):
                     elif t.tag == 'K':
                         self.assertEqual(t.data, keyEncoded)
 
-
-
     def testTokenProcess(self):
         from music21.abcFormat import testFiles
 
@@ -3171,7 +3255,6 @@ class Test(unittest.TestCase):
             handler.tokenize(tf)
             handler.tokenProcess()
 
-
     def testNoteParse(self):
         from music21 import key
 
diff --git a/music21/abcFormat/translate.py b/music21/abcFormat/translate.py
index e33e522d3..5dc64010a 100644
--- a/music21/abcFormat/translate.py
+++ b/music21/abcFormat/translate.py
@@ -41,7 +41,6 @@ from music21 import harmony
 environLocal = environment.Environment('abcFormat.translate')
 
 
-
 def abcToStreamPart(abcHandler, inputM21=None, spannerBundle=None):
     '''
     Handler conversion of a single Part of a multi-part score.
@@ -723,7 +722,8 @@ class Test(unittest.TestCase):
         #m1.show('t')
         # notes are shown as being on beat 2 and 3
         # environLocal.printDebug(['m1.notesAndRests.activeSite', m1.notesAndRests.activeSite])
-        # environLocal.printDebug(['m1.notesAndRests[0].activeSite', m1.notesAndRests[0].activeSite])
+        # environLocal.printDebug(['m1.notesAndRests[0].activeSite',
+        #     m1.notesAndRests[0].activeSite])
 
         #self.assertEqual(m1.notesAndRests.activeSite)
 


Test patch:
diff --git a/music21/abcFormat/testFiles.py b/music21/abcFormat/testFiles.py
index 667d8c3d2..319562a18 100644
--- a/music21/abcFormat/testFiles.py
+++ b/music21/abcFormat/testFiles.py
@@ -548,7 +548,7 @@ V:1
 fz((6:4F,//A,//C//F//A//c// e/d/)dz
 '''
 
-czerny_csharp = '''
+czernyCsharp = '''
 X:4
 T:D Fragment
 C:Czerny
@@ -558,19 +558,81 @@ L:1/16
 CEDF EGFA GBAc Bdce|]
 '''
 
+carryThrough = '''
+X:213
+T:Through Measure
+L:1/16
+Q:1/4=104
+M:4/4
+K:G
+V:1 treble
+V:1
+(=fe^d^c _BcdB) fB=BB c=cc^c|c4d2f2 ^G2=f2B2^A2-|A4F4 A2B2  c2f2|]
+'''
+
+tieOver = '''
+X:213
+T:Tie Through Measure
+L:1/8
+M:4/4
+K:G
+V:1 treble
+V:1
+z8 ^G=fB^A-|A2F2 AB cf|]
+'''
+
+directiveCarryOctave = '''
+%abc-2.1
+%%propagate-accidentals octave
+X:213
+T:Directive Octave
+L:1/8
+M:4/4
+K:G
+V:1 treble
+V:1
+g^G_ag a=ffF|=F2^c2 FB =ca|]
+'''
+
+directiveCarryPitch = '''
+%abc-2.1
+%%propagate-accidentals pitch
+X:213
+T:Directive Pitch 
+L:1/8
+M:4/4
+K:G
+V:1 treble
+V:1
+g^G_ag a=ffF|=F2^c2 FB =ca|]
+'''
+
+directiveCarryNot = '''
+%abc-2.1
+%%propagate-accidentals not
+X:213
+T:Directive Not
+L:1/8
+M:4/4
+K:G
+V:1 treble
+V:1
+g^G_ag a=ffF|=F2^c2 FB =ca|]
+
+'''
+
+
 # ------------------------------------------------------------------------------
 
 ALL  = [fyrareprisarn, mysteryReel, fullRiggedShip, aleIsDear, kitchGirl,
         williamAndNancy, morrisonsJig, hectorTheHero, kingOfTheFairies,
         sicutRosa, theAleWifesDaughter, theBeggerBoy, theBattleOfTheSnaBas,
-        czerny_csharp,
         draughtOfAle,
         valentineJigg,
         testPrimitive, testPrimitivePolyphonic, testPrimitiveTuplet
     ]
 
 
-
 def get(contentRequest):
     '''Get test material by type of content
 
@@ -579,7 +641,6 @@ def get(contentRequest):
     pass
 
 
-
 # ------------------------------------------------------------------------------
 class Test(unittest.TestCase):
 
@@ -592,9 +653,8 @@ class Test(unittest.TestCase):
         from music21.musicxml import m21ToXml
 
         af = abcFormat.ABCFile()
-
         GEX = m21ToXml.GeneralObjectExporter()
-
+        
         for i, tf in enumerate(ALL):
             ah = af.readstr(tf)
             title = ah.getTitle()
@@ -608,12 +668,112 @@ class Test(unittest.TestCase):
             except UnicodeDecodeError as ude:
                 environLocal.warn('About to fail on ABC file #{}'.format(i))
                 raise ude
-            
-            if title == 'D Fragment':
-                sharps = s.parts[0].keySignature.sharps
-                self.assertEqual(sharps, 7, 'C# key signature should be parsed as 7 sharps')
+    
+    def testKeySignatures(self):      
+        from music21 import abcFormat
+        from music21.abcFormat import translate
+        af = abcFormat.ABCFile()
+        ah = af.readstr(czernyCsharp)
+        s = translate.abcToStreamScore(ah)
+        sharps = s.parts[0].keySignature.sharps
+        self.assertEqual(sharps, 7, 'C# key signature should be parsed as 7 sharps')
+                
+    def testAbc21(self):
+        from music21 import abcFormat, note
+        from music21.abcFormat import translate
+
+        af = abcFormat.ABCFile(abcVersion=(2,1,0))
+        ah = af.readstr(carryThrough)
+        title = ah.getTitle()
+        environLocal.printDebug([title])
+        s = translate.abcToStreamScore(ah)
+        notes = s.flat.getElementsByClass(note.Note)
+        cSharp = notes[3]
+        cThrough = notes[5]
+        self.assertEqual(cSharp.pitch.midi, cThrough.pitch.midi, "Sharp does not carry through measure")
+        bFlat = notes[4]
+        bLast = notes[7]
+        self.assertEqual(bFlat.pitch.midi, bLast.pitch.midi, "Flat does not carry through measure")
+        bNat = notes[10]
+        bNatNext = notes[11]
+        self.assertEqual(bNat.pitch.midi, bNatNext.pitch.midi, "Natural does not carry through measure")
+        self.assertEqual(notes[12].pitch.midi, 73, "Sharp does not carry through measure")
+        self.assertEqual(notes[13].pitch.midi, 72, "Natural is ignored")
+        self.assertEqual(notes[14].pitch.midi, 72, "Natural does not carry through measure")
+        self.assertEqual(notes[16].pitch.midi, 72, "Sharp carries over measure incorrectly")
+        self.assertEqual(notes[17].pitch.midi, 74, "Sharp (D5) carries over measure incorrectly")
+        self.assertEqual(notes[18].pitch.midi, 78, "Natural (F5) carries over measure incorrectly")
+        # TODO: Carrying an accidental into the next measure with a tie does not work.
+        # notes = s.flat.getElementsByClass(note.Note)
+        # self.assertEqual(notes[4].pitch.midi, 70, "Tied note loses it sharp")
+        # self.assertEqual(notes[6].pitch.midi, 69, "Tied-over sharp persists past the tie")
+
+    def testAbc21DirectiveCarryPitch(self):
+        from music21 import abcFormat, note
+        from music21.abcFormat import translate
+
+        af = abcFormat.ABCFile()
+        ah = af.readstr(directiveCarryPitch)
+        s = translate.abcToStreamScore(ah)
+        notes = s.flat.getElementsByClass(note.Note)
+        gSharp = notes[1]
+        g8va = notes[3]
+        self.assertEqual(gSharp.pitch.midi % 12, g8va.pitch.midi % 12, "Sharp does not carry through measure")
+        aFlat = notes[2]
+        a = notes[4]
+        self.assertEqual(aFlat.pitch.midi, a.pitch.midi, "Flat does not carry through measure")
+        fNat = notes[5]
+        f = notes[6]
+        f8ba = notes[7]
+        self.assertEqual(fNat.pitch.midi, f.pitch.midi, "Natural does not carry through measure")
+        self.assertEqual(fNat.pitch.midi % 12, f8ba.pitch.midi % 12, "Natural does not carry through measure")
+        self.assertEqual(notes[8].pitch.midi, 65, "Natural is ignored")
+        self.assertEqual(notes[12].pitch.midi, 72, "Natural is ignored")
+        
+    def testAbc21DirectiveCarryOctave(self):
+        from music21 import abcFormat, note
+        from music21.abcFormat import translate
 
+        af = abcFormat.ABCFile()
+        ah = af.readstr(directiveCarryOctave)
+        s = translate.abcToStreamScore(ah)
+        notes = s.flat.getElementsByClass(note.Note)
+        gSharp = notes[1]
+        g8va = notes[3]
+        self.assertTrue(gSharp.pitch.midi % 12 > g8va.pitch.midi % 12, "Sharp carries beyond its octave")
+        aFlat = notes[2]
+        a = notes[4]
+        self.assertEqual(aFlat.pitch.midi, a.pitch.midi, "Flat does not carry through measure")
+        fNat = notes[5]
+        f = notes[6]
+        f8ba = notes[7]
+        self.assertEqual(fNat.pitch.midi, f.pitch.midi, "Natural does not carry through measure")
+        self.assertTrue(fNat.pitch.midi % 12 < f8ba.pitch.midi % 12, "Natural carries beyond its octave")
+        self.assertEqual(notes[8].pitch.midi, 65, "Natural is ignored")
+        self.assertEqual(notes[12].pitch.midi, 72, "Natural is ignored")
         
+    def testAbc21DirectiveCarryNot(self):
+        from music21 import abcFormat, note
+        from music21.abcFormat import translate
+
+        af = abcFormat.ABCFile()
+        ah = af.readstr(directiveCarryNot)
+        s = translate.abcToStreamScore(ah)
+        notes = s.flat.getElementsByClass(note.Note)
+        gSharp = notes[1]
+        g8va = notes[3]
+        self.assertTrue(gSharp.pitch.midi % 12 > g8va.pitch.midi % 12, "Sharp carries beyond its octave")
+        aFlat = notes[2]
+        a = notes[4]
+        self.assertTrue(aFlat.pitch.midi < a.pitch.midi, "Flat carries through measure")
+        fNat = notes[5]
+        f = notes[6]
+        f8ba = notes[7]
+        self.assertTrue(fNat.pitch.midi < f.pitch.midi, "Natural carries through measure")
+        self.assertTrue(fNat.pitch.midi % 12 < f8ba.pitch.midi % 12, "Natural carries beyond its octave")
+        self.assertEqual(notes[8].pitch.midi, 65, "Natural is ignored")
+        self.assertEqual(notes[12].pitch.midi, 72, "Natural is ignored")
+                        
 if __name__ == '__main__':
     import music21
     #music21.converter.parse(reelsABC21, format='abc').scores[1].show()