def rnArray(self, major=True, minor=True): ''' Prepares the RNs from the figures. ''' self.allRomanListOfLists = [] for fig in self.figs: thisFigListOfLists = [] for accidental in ['b', '', '#']: for scaleDegree in ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii']: for chordType in [ scaleDegree.lower() + 'o', scaleDegree.lower(), scaleDegree.upper(), scaleDegree.upper() + '+' ]: # Need to do accidental again for each element in figure. Maybe above? componentParts = [accidental, chordType, str(fig)] joinedRN = ''.join(componentParts) majRN = roman.RomanNumeral(joinedRN, 'C') minRN = roman.RomanNumeral(joinedRN, 'a') outTrio = (joinedRN, [p.name for p in majRN.pitches], [p.name for p in minRN.pitches]) thisFigListOfLists.append(outTrio) self.allRomanListOfLists.append(thisFigListOfLists)
def testMixture(self): for fig in ['i', 'iio', 'bIII', 'iv', 'v', 'bVI', 'bVII', 'viio7']: # True, major key: self.assertTrue(roman.RomanNumeral(fig, 'A').isMixture()) # False, minor key: self.assertFalse(roman.RomanNumeral(fig, 'a').isMixture()) for fig in ['I', 'ii', '#iii', 'IV', 'viiø7']: # False, major key: self.assertFalse(roman.RomanNumeral(fig, 'A').isMixture()) # True, minor key: self.assertTrue(roman.RomanNumeral(fig, 'a').isMixture())
def testTypeParses(self): ''' Tests successful init on a range of supported objects (score, part, even RomanNumeral). ''' s = stream.Score() romanText.writeRoman.RnWriter(s) # Works on a score p = stream.Part() romanText.writeRoman.RnWriter(p) # or on a part s.insert(p) romanText.writeRoman.RnWriter(s) # or on a score with part m = stream.Measure() RnWriter(m) # or on a measure v = stream.Voice() # or theoretically on a voice, but will be empty for lack of measures emptyWriter = RnWriter(v) self.assertEqual(emptyWriter.combinedList, [ 'Composer: Composer unknown', 'Title: Title unknown', 'Analyst: ', 'Proofreader: ', '', ]) rn = roman.RomanNumeral('viio6', 'G') RnWriter(rn) # and even (perhaps dubiously) directly on other music21 objects
def roman_to_pcp(figure: str, key: str, root_0: bool = False, return_root: bool = False): """ Converts a figure and key (e.g. 'ii' , 'Db') via a RomanNumeral object into a pitch classes profile (PCP). >>> roman_to_pcp('ii', 'Db') [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0] Optionally, rotates the PCP to situate the root at position 0 >>> roman_to_pcp('ii', 'Db', root_0=True) [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0] Optionally, also return the root >>> roman_to_pcp('ii', 'Db', root_0=True, return_root=True) ([1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], 3) Note: requires music21. """ from music21 import roman rn = roman.RomanNumeral(figure, key) pitch_classes = [x.pitch.pitchClass for x in rn] # e.g. [2, 5, 9] pcp = pitch_class_list_to_profile( pitch_classes) # pitch classes profile [0, 0, 1 ... root_pc = rn.root().pitchClass if root_0: pcp = rotate(pcp, -root_pc) # sic - if return_root: return pcp, root_pc else: return pcp
def testPitchGeneration(): s = Scale("ionian") rhythmSeq = l._generateRhythmLick() scaleDegreeSeq = l._generateScaleDegreeLick(rhythmSeq, s) midiNotes = l._convertScaleDegreesToMidiNotes(scaleDegreeSeq, s) p1 = stream.Part() p2 = stream.Part() s = stream.Score() initialPause = 8 - sum(rhythmSeq) for rn in ["I", "ii", "iii", "IV", "V", "vi", "viio"]: for i in range(8): rno = roman.RomanNumeral(rn) rno.quarterLength = 1 p2.append(rno) n = note.Rest() n.quarterLength = initialPause p1.append(n) for r, p in zip(rhythmSeq, midiNotes): n = note.Note() n.duration.quarterLength = r n.pitch.midi = p p1.append(n) s.insert(0, p1) s.insert(0, p2) s.show()
def testSingleChordFeatures(self): rn = roman.RomanNumeral('I') # Tonic major profile = [ 309.17, 37.31, 10.09, 376.62, 10.75, 21.43, 316.04, 1.02, 19.79, 33.68, 24.33, 22.97 ] # c diminished testFeaturesSet = SingleChordFeatures(rn, profile) for pair in ( ('chordQualityVector', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]), # major ('thirdTypeVector', [0, 1, 0]), # major ('fifthTypeVector', [0, 1, 0, 0]), # perfect ('seventhTypeVector', [0, 0, 0, 1]), # None ('rootPitchClassVector', [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), # C ('hauptFunctionVector', [1, 0, 0, 0, 0, 0, 0]), # T ('functionVector', [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), # T ('chosenChordPCPVector', [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]), # C major ('bestFitChordPCPVector', [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0]), # c diminished ( 'chordTypeMatchVector', # False, major vs dim. [0]), ( 'chordRotationMatchVector', # True, C = C [1]), ('distanceToBestFitChordPCPVector', [0.307]), ('distanceToChosenChordVector', [0.7285]), ('fullChordCommonnessVector', [1]), # Most common = 1 ('simplifiedChordCommonnessVector', [0.908]) # Still very common ): vector = getattr(testFeaturesSet, pair[0]) self.assertEqual(vector, pair[1])
def getLocalKey(local_key, global_key, convertDCMLToM21=False): ''' Re-casts comparative local key (e.g. 'V of G major') in its own terms ('D'). >>> romanText.tsvConverter.getLocalKey('V', 'G') 'D' >>> romanText.tsvConverter.getLocalKey('ii', 'C') 'd' By default, assumes an m21 input, and operates as such: >>> romanText.tsvConverter.getLocalKey('vii', 'a') 'g#' Set convert=True to convert from DCML to m21 formats. Hence; >>> romanText.tsvConverter.getLocalKey('vii', 'a', convertDCMLToM21=True) 'g' ''' if convertDCMLToM21: local_key = characterSwaps(local_key, minor=is_minor(global_key[0]), direction='DCML-m21') asRoman = roman.RomanNumeral(local_key, global_key) rt = asRoman.root().name if asRoman.isMajorTriad(): newKey = rt.upper() elif asRoman.isMinorTriad(): newKey = rt.lower() else: # pragma: no cover raise ValueError('local key must be major or minor') return newKey
def testIgnoresInversion(self): """ If the input is a string, then no match will be found (returns False). If the input is a roman.RomanNumeral object they will be ignored (using the romanNumeralAlone attribute). """ self.assertEqual(figureToFunction('V42'), False) self.assertEqual(figureToFunction(roman.RomanNumeral('i6')), 't')
def _copyMultipleMeasures(t, p, kCurrent): ''' Given a RomanText token for a RTMeasure, a Part used as the current container, and the current Key, return a Measure range copied from the past of the Part. This is used for cases such as: m23-25 = m20-22 ''' # the key provided needs to be the current key #environLocal.printDebug(['calling _copyMultipleMeasures()']) targetNumbers, unused_targetRepeat = t.getCopyTarget() if len(targetNumbers) == 1: # this is an encoding error raise RomanTextTranslateException('a multiple measure range cannot copy a single measure') # TODO: ignoring repeat letters targetStart = targetNumbers[0] targetEnd = targetNumbers[1] if t.number[1] - t.number[0] != targetEnd - targetStart: raise RomanTextTranslateException('both the source and destination sections need to have the same number of measures') elif t.number[0] < targetEnd: raise RomanTextTranslateException('the source section cannot overlap with the destination section') measures = [] for mPast in p.getElementsByClass('Measure'): if mPast.number in range(targetStart, targetEnd +1): try: m = copy.deepcopy(mPast) except TypeError: raise RomanTextTranslateException('Failed to copy measure %d to measure range %d-%d: did you perhaps parse an RTOpus object with romanTextToStreamScore instead of romanTextToStreamOpus?' % (mPast.number, targetStart, targetEnd)) m.number = t.number[0] + mPast.number - targetStart measures.append(m) # update all keys allRNs = m.getElementsByClass('RomanNumeral').elements for rnPast in allRNs: if kCurrent is None: # should not happen raise RomanTextTranslateException('attempting to copy a measure but no past key definitions are found') if rnPast.followsKeyChange is True: kCurrent = rnPast.key elif rnPast.pivotChord is not None: kCurrent = rnPast.pivotChord.key else: rnPast.key = kCurrent if rnPast.secondaryRomanNumeral is not None: from music21 import roman newRN = roman.RomanNumeral(rnPast.figure, kCurrent) newRN.duration = copy.deepcopy(rnPast.duration) newRN.lyrics = copy.deepcopy(rnPast.lyrics) m.replace(rnPast, newRN) if mPast.number == targetEnd: break return measures, kCurrent
def testSimplified(self): rn = roman.RomanNumeral('III', 'f') fn1 = romanToFunction(rn) self.assertIs(fn1, HarmonicFunction.TONIC_MINOR_PARALLELKLANG_MAJOR) self.assertEqual(str(fn1), 'tP') fn2 = romanToFunction(rn, onlyHauptHarmonicFunction=True) self.assertIs(fn2, HarmonicFunction.TONIC_MINOR) self.assertEqual(str(fn2), 't')
def testIntervalMatch(self): rns = [roman.RomanNumeral(x) for x in ['i', 'iiø65', 'V7']] intervalsType1 = ['M2', 'P4'] self.assertTrue(intervalMatch(rns, intervalsType1, bassOrRoot='root')) self.assertFalse(intervalMatch(rns, intervalsType1, bassOrRoot='bass')) intervalsType2 = ['P4', 'M2'] self.assertTrue(intervalMatch(rns, intervalsType2, bassOrRoot='bass')) self.assertFalse(intervalMatch(rns, intervalsType2, bassOrRoot='root'))
def tabToM21(self): ''' Creates and returns a music21.roman.RomanNumeral() object from a TabChord with all shared attributes. NB: call changeRepresentation() first if .representationType is not 'm21' but you plan to process it with m21 (e.g. moving it into a stream). >>> tabCd = romanText.tsvConverter.TabChord() >>> tabCd.numeral = 'vii' >>> tabCd.global_key = 'F' >>> tabCd.local_key = 'V' >>> tabCd.representationType = 'm21' >>> m21Ch = tabCd.tabToM21() Now we can check it's a music21 RomanNumeral(): >>> m21Ch.figure 'vii' ''' if self.numeral: if self.form: if self.figbass: combined = ''.join([self.numeral, self.form, self.figbass]) else: combined = ''.join([self.numeral, self.form]) else: combined = self.numeral if self.relativeroot: # special case requiring '/'. combined = ''.join([combined, '/', self.relativeroot]) localKeyNonRoman = getLocalKey(self.local_key, self.global_key) thisEntry = roman.RomanNumeral(combined, localKeyNonRoman) thisEntry.quarterLength = self.length thisEntry.op = self.op thisEntry.no = self.no thisEntry.mov = self.mov thisEntry.pedal = self.pedal thisEntry.phraseend = None else: # handling case of '@none' thisEntry = note.Rest() thisEntry.quarterLength = self.length return thisEntry
def getLocalKey(local_key: chord.Chord, global_key: key.Key): ''' Works out the quality (major / minor) of a local key relative to a global one Note similar to a function in romanText.tsvConverter. ''' asRoman = roman.RomanNumeral(local_key, global_key) rt = asRoman.root().name if asRoman.isMajorTriad(): return rt.upper() elif asRoman.isMinorTriad(): return rt.lower() else: # TODO check redundancy - keys checks (and potential fails) in roman.RomanNumeral raise ValueError( f'The local_key (currently {local_key}) must be a major or minor triad.' )
def _copySingleMeasure(t, p, kCurrent): '''Given a RomanText token, a Part used as the current container, and the current Key, return a Measure copied from the past of the Part. This is used in cases of definitions such as: m23=m21 ''' # copy from a past location; need to change key #environLocal.printDebug(['calling _copySingleMeasure()']) targetNumber, unused_targetRepeat = t.getCopyTarget() if len(targetNumber) > 1: # this is an encoding error raise RomanTextTranslateException( 'a single measure cannot define a copy operation for multiple measures' ) # TODO: ignoring repeat letters target = targetNumber[0] for mPast in p.getElementsByClass('Measure'): if mPast.number == target: try: m = copy.deepcopy(mPast) except TypeError: raise RomanTextTranslateException( 'Failed to copy measure %d: did you perhaps parse an RTOpus object with romanTextToStreamScore instead of romanTextToStreamOpus?' % (mPast.number)) m.number = t.number[0] # update all keys for rnPast in m.getElementsByClass('RomanNumeral'): if kCurrent is None: # should not happen raise RomanTextTranslateException( 'attempting to copy a measure but no past key definitions are found' ) if rnPast.followsKeyChange is True: kCurrent = rnPast.key elif rnPast.pivotChord is not None: kCurrent = rnPast.pivotChord.key else: rnPast.key = kCurrent if rnPast.secondaryRomanNumeral is not None: from music21 import roman newRN = roman.RomanNumeral(rnPast.figure, kCurrent) newRN.duration = copy.deepcopy(rnPast.duration) newRN.lyrics = copy.deepcopy(rnPast.lyrics) m.replace(rnPast, newRN) break return m, kCurrent
def _romanFromLyric(self, lyric): ''' Converts lyrics in recognised format into m21 Roman Numeral objects. Format: 'Key: Figure' for first entry and all key changes; otherwise just 'Figure'. Includes the following substitutions: all spaces (including non-breaking spaces) with nothing; '-' with 'b' for flats; bracket types to accept e.g. (no5) as well as the official [no5]; and 'sus' with '[addX]' for suspensions/added notes. ''' lyric = lyric.replace(' ', '') lyric = lyric.replace('\xa0', '') lyric = lyric.replace('-', 'b') lyric = lyric.replace('(', '[') lyric = lyric.replace(')', ']') if 'sus' in lyric: lyric = lyric.replace('sus', '[add') lyric += ']' if ':' in lyric: self.prevailingKey, figure = lyric.split(':') else: figure = lyric # hence self.prevailingKey asRoman = roman.RomanNumeral(figure, self.prevailingKey, # sixthMinor=roman.Minor67Default.CAUTIONARY, # seventhMinor=roman.Minor67Default.CAUTIONARY, ) if asRoman.figure: # TODO: better test? return asRoman else: return False
def __init__(self, rn: Union[roman.RomanNumeral, str], sourceUsageProfile: list, returnOneHot: bool = True, comparison_type: str = 'L1'): if isinstance(rn, str): rn = roman.RomanNumeral(rn) self.rn = rn self.sourceUsageProfile = sourceUsageProfile self.returnOneHot = returnOneHot self.comparison_type = comparison_type self.chordQualityVector = None self.thirdTypeVector = None self.fifthTypeVector = None self.seventhTypeVector = None self.rootPitchClassVector = None self.getBasicChordFeatures() self.thisFn = str(analysis.harmonicFunction.romanToFunction(self.rn)) self.hauptFunctionVector = self.getHauptFunctionVector() self.functionVector = self.getFunctionVector() self.chosenChordPCPVector = self.getChosenChordPCPVector() self.bestFitChordPCPVector = [0] * 12 self.distanceToChosenChordVector = [0] self.distanceToBestFitChordPCPVector = [0] self.chordTypeMatchVector = [0] self.chordRotationMatchVector = [0] self.evaluateBestFit() self.fullChordCommonnessVector = self.getFullChordCommonnessVector() self.simplifiedChordCommonnessVector = self.getSimplifiedChordCommonnessVector( )
def functionToRoman( thisHarmonicFunction: HarmonicFunction, keyOrScale: t.Union[key.Key, scale.Scale, str] = 'C' ) -> t.Optional[roman.RomanNumeral]: ''' Takes harmonic function labels (such as 'T' for major tonic) with a key (keyOrScale, default = 'C') and returns the corresponding :class:`~music21.roman.RomanNumeral` object. >>> analysis.harmonicFunction.functionToRoman('T') <music21.roman.RomanNumeral I in C major> The harmonicFunction argument can be a string (as shown), though strictly speaking, it's handled through a special HarmonicFunction enum object. >>> fn = analysis.harmonicFunction.HarmonicFunction.TONIC_MAJOR >>> str(fn) 'T' >>> analysis.harmonicFunction.functionToRoman(fn).figure 'I' As with Roman numerals, this is case sensitive. For instance, 't' indicates a minor tonic as distinct from the major tonic, 'T'. >>> analysis.harmonicFunction.functionToRoman('t').figure 'i' There are 18 main functional labels supported in all, for the three functional categories (T for tonic, S for subdominant, and D for dominant) and three relevant transformation types (none, P, and G) all in upper and lower case (for major/minor): T, Tp, Tg, t, tP, tG, S, Sp, Sg, s, sP, sG, D, Dp, Dg, d, dP, dG. Note that this module uses terminology from modern German music theory where Functional notation ('HarmonicFunctionstheorie') is typically used throughout the curriculum in preference over Roman numerals ('Stufentheorie'). First, note the false friend: here 'P' for 'Parallel' connects a major triad with the minor triad a minor third below (e.g. C-a). (in English-speaking traditions this would usually be 'relative'). Second, note that this module uses 'G' (and 'g'), standing for 'Gegenklänge' or 'Gegenparallelen'. 'L' (and 'l') for Leittonwechselklänge is equivalent to this. (Again, 'G' is more common in modern German-language music theory). Use the keyOrScale argement to specify a key. This makes a difference where 6th and 7th degrees of minor are involved. >>> analysis.harmonicFunction.functionToRoman('sP', keyOrScale='C').figure 'bVI' >>> analysis.harmonicFunction.functionToRoman('sP', keyOrScale='a').figure 'VI' Some of the 18 main functions overlap, with two functional labels referring to the same Roman numeral figure. For instance both 'Tg' and 'Dp' simply map to 'iii': >>> analysis.harmonicFunction.functionToRoman('Tp').figure 'vi' >>> analysis.harmonicFunction.functionToRoman('Sg').figure 'vi' The reverse operation is handled by the complementary :func:`~music21.analysis.harmonicFunction.romanToFunction`. In this case, :func:`~music21.analysis.harmonicFunction.romanToFunction` follows the convention of preferring the P-version over alternatives. >>> rn = roman.RomanNumeral('vi') >>> str(analysis.harmonicFunction.romanToFunction(rn)) 'Tp' ''' if isinstance(keyOrScale, str): keyOrScale = key.Key(keyOrScale) referenceTuples = functionFigureTuplesMajor if isinstance(keyOrScale, key.Key) and keyOrScale.mode == 'minor': referenceTuples = functionFigureTuplesMinor try: figure = referenceTuples[thisHarmonicFunction] except KeyError: return None return roman.RomanNumeral(figure, keyOrScale)
def testIsOfType(self): self.assertTrue(isOfType(roman.RomanNumeral('I'), [6, 10, 1])) self.assertTrue(isOfType(chord.Chord('G- B-- D-'), '3-11A'))
def combine_slice_group(self, list_of_slices: list,): """ Shared method for synthesising a list of slice dicts into one dict with values for From the first slice: 'start offset' 'start measure' 'key' 'chord': note pop'd when grouping by key From the last slice: 'end offset' 'end measure' * From all the slices: 'profile' 'quarter length' Also 'measure length' from the difference. * * NB: end measure and measure length only reliable in so far as the original slices data includes splits at the start of each measure. Potentially off by one otherwise. Abstracted enough to support grouping by changes of: - key (get_profiles_by_key > self.profiles_by_key) - chord (get_profiles_by_chord > self.profiles_by_chord) - measure (get_profiles_by_measure > self.profiles_by_measure) This method also gathers feature information as described in chord_features.get_features if self.include_features is set to True (at the class init) Note how this relates to arguments at the class init: - self.include_features must be True for this information to be collected - self.features_to_use defines which. and in write_distributions (option to write that information). """ first_slice = list_of_slices[0] last_slice = list_of_slices[-1] entry = {'key': first_slice['key'], 'chord': first_slice['chord'], # NB: pop'd when grouping by key 'start offset': first_slice['offset'], 'start measure': first_slice['measure'], 'end offset': last_slice['offset'] + last_slice['length'], 'end measure': last_slice['measure'], 'profile': [0] * 12, # init 'quarter length': 0, # init } for s in list_of_slices: for pc in range(12): entry['profile'][pc] += s['profile'][pc] * s['length'] entry['quarter length'] += s['length'] # Note: ^ Alternatively, sum([s['quarter length'] for s in list_of_slices] # Note: Likewise distributions # Finishing up entry['quarter length'] = round(entry['quarter length'], self.round_places) entry['profile'] = self.round_and_norm(entry['profile']) entry['measure length'] = entry['end measure'] - entry['start measure'] if self.include_features: from music21 import roman rn = roman.RomanNumeral(entry['chord'], entry['key']) features = chord_features.SingleChordFeatures(rn, entry['profile']) for feat in self.features_to_use: # see below for list entry[feat] = getattr(features, feat) return entry
def romanTextToStreamScore(rtHandler, inputM21=None): '''The main processing module for single-movement RomanText works. Given a romanText handler or string, return or fill a Score Stream. ''' # accept a string directly; mostly for testing if common.isStr(rtHandler): rtf = rtObjects.RTFile() rtHandler = rtf.readstr(rtHandler) # return handler, processes tokens # this could be just a Stream, but b/c we are creating metadata, perhaps better to match presentation of other scores. from music21 import metadata from music21 import stream from music21 import note from music21 import meter from music21 import key from music21 import roman from music21 import tie if inputM21 == None: s = stream.Score() else: s = inputM21 # metadata can be first md = metadata.Metadata() s.insert(0, md) p = stream.Part() # ts indication are found in header, and also found elsewhere tsCurrent = meter.TimeSignature('4/4') # create default 4/4 tsSet = False # store if set to a measure lastMeasureToken = None lastMeasureNumber = 0 previousRn = None keySigCurrent = None keySigSet = True # set a keySignature foundAKeySignatureSoFar = False kCurrent, unused_prefixLyric = _getKeyAndPrefix( 'C') # default if none defined prefixLyric = '' repeatEndings = {} rnKeyCache = {} for t in rtHandler.tokens: try: # environLocal.printDebug(['token', t]) if t.isTitle(): md.title = t.data elif t.isWork(): md.alternativeTitle = t.data elif t.isPiece(): md.alternativeTitle = t.data elif t.isComposer(): md.composer = t.data elif t.isMovement(): md.movementNumber = t.data elif t.isTimeSignature(): tsCurrent = meter.TimeSignature(t.data) tsSet = False # environLocal.printDebug(['tsCurrent:', tsCurrent]) elif t.isKeySignature(): if t.data == "": keySigCurrent = key.KeySignature(0) elif t.data == "Bb": keySigCurrent = key.KeySignature(-1) else: pass # better to print a message # environLocal.printDebug(['still need to write a generic RomanText KeySignature routine. this is just temporary']) # raise RomanTextTranslateException("still need to write a generic RomanText KeySignature routine. this is just temporary") keySigSet = False # environLocal.printDebug(['keySigCurrent:', keySigCurrent]) foundAKeySignatureSoFar = True elif t.isMeasure(): # environLocal.printDebug(['handling measure token:', t]) #if t.number[0] % 10 == 0: # print "at number " + str(t.number[0]) if t.variantNumber is not None: # environLocal.printDebug(['skipping variant: %s' % t]) continue if t.variantLetter is not None: # environLocal.printDebug(['skipping variant: %s' % t]) continue # if this measure number is more than 1 greater than the last # defined measure number, and the previous chord is not None, # then fill with copies of the last-defined measure if ((t.number[0] > lastMeasureNumber + 1) and (previousRn is not None)): for i in range(lastMeasureNumber + 1, t.number[0]): mFill = stream.Measure() mFill.number = i newRn = copy.deepcopy(previousRn) newRn.lyric = "" # set to entire bar duration and tie newRn.duration = copy.deepcopy(tsCurrent.barDuration) if previousRn.tie is None: previousRn.tie = tie.Tie('start') else: previousRn.tie.type = 'continue' # set to stop for now; may extend on next iteration newRn.tie = tie.Tie('stop') previousRn = newRn mFill.append(newRn) appendMeasureToRepeatEndingsDict( lastMeasureToken, mFill, repeatEndings, i) p._appendCore(mFill) lastMeasureNumber = t.number[0] - 1 lastMeasureToken = t # create a new measure or copy a past measure if len(t.number) == 1 and t.isCopyDefinition: # if not a range p.elementsChanged() m, kCurrent = _copySingleMeasure(t, p, kCurrent) p._appendCore(m) lastMeasureNumber = m.number lastMeasureToken = t romans = m.getElementsByClass(roman.RomanNumeral, returnStreamSubClass='list') if len(romans) > 0: previousRn = romans[-1] elif len(t.number) > 1: p.elementsChanged() measures, kCurrent = _copyMultipleMeasures(t, p, kCurrent) p.append(measures) # appendCore does not work with list lastMeasureNumber = measures[-1].number lastMeasureToken = t romans = measures[-1].getElementsByClass( roman.RomanNumeral, returnStreamSubClass='list') if len(romans) > 0: previousRn = romans[-1] else: m = stream.Measure() m.number = t.number[0] appendMeasureToRepeatEndingsDict(t, m, repeatEndings) lastMeasureNumber = t.number[0] lastMeasureToken = t if not tsSet: m.timeSignature = tsCurrent tsSet = True # only set when changed if not keySigSet and keySigCurrent is not None: m.insert(0, keySigCurrent) keySigSet = True # only set when changed o = 0.0 # start offsets at zero previousChordInMeasure = None pivotChordPossible = False numberOfAtoms = len(t.atoms) setKeyChangeToken = False # first RomanNumeral object after a key change should have this set to True for i, a in enumerate(t.atoms): if isinstance(a, rtObjects.RTKey) or \ ((foundAKeySignatureSoFar == False) and \ (isinstance(a, rtObjects.RTAnalyticKey))): # found a change of Key+KeySignature or # just found a change of analysis but no keysignature so far # environLocal.printDebug(['handling key token:', a]) try: # this sets the key and the keysignature kCurrent, pl = _getKeyAndPrefix(a) prefixLyric += pl except: raise RomanTextTranslateException( 'cannot get key from %s in line %s' % (a.src, t.src)) # insert at beginning of measure if at beginning -- for things like pickups. if m.number < 2: m._insertCore(0, kCurrent) else: m._insertCore(o, kCurrent) foundAKeySignatureSoFar = True setKeyChangeToken = True elif isinstance(a, rtObjects.RTKeySignature): try: # this sets the keysignature but not the prefix text thisSig = a.getKeySignature() except: raise RomanTextTranslateException( 'cannot get key from %s in line %s' % (a.src, t.src)) #insert at beginning of measure if at beginning -- for things like pickups. if m.number < 2: m._insertCore(0, thisSig) else: m._insertCore(o, thisSig) foundAKeySignatureSoFar = True elif isinstance(a, rtObjects.RTAnalyticKey): # just a change in analyzed key, not a change in anything else #try: # this sets the key, not the keysignature kCurrent, pl = _getKeyAndPrefix(a) prefixLyric += pl setKeyChangeToken = True #except: # raise RomanTextTranslateException('cannot get key from %s in line %s' % (a.src, t.src)) elif isinstance(a, rtObjects.RTBeat): # set new offset based on beat try: o = a.getOffset(tsCurrent) except ValueError: raise RomanTextTranslateException( "cannot properly get an offset from beat data %s under timeSignature %s in line %s" % (a.src, tsCurrent, t.src)) if (previousChordInMeasure is None and previousRn is not None and o > 0): # setting a new beat before giving any chords firstChord = copy.deepcopy(previousRn) firstChord.quarterLength = o firstChord.lyric = "" if previousRn.tie == None: previousRn.tie = tie.Tie('start') else: previousRn.tie.type = 'continue' firstChord.tie = tie.Tie('stop') previousRn = firstChord previousChordInMeasure = firstChord m._insertCore(0, firstChord) pivotChordPossible = False elif isinstance(a, rtObjects.RTNoChord): # use source to evaluation roman rn = note.Rest() if pivotChordPossible == False: # probably best to find duration if previousChordInMeasure is None: pass # use default duration else: # update duration of previous chord in Measure oPrevious = previousChordInMeasure.getOffsetBySite( m) newQL = o - oPrevious if newQL <= 0: raise RomanTextTranslateException( 'too many notes in this measure: %s' % t.src) previousChordInMeasure.quarterLength = newQL prefixLyric = "" m._insertCore(o, rn) previousChordInMeasure = rn previousRn = rn pivotChordPossible = False elif isinstance(a, rtObjects.RTChord): # use source to evaluation roman try: asrc = a.src # if kCurrent.mode == 'minor': # if asrc.lower().startswith('vi'): #vi or vii w/ or w/o o # if asrc.upper() == a.src: # VI or VII to bVI or bVII # asrc = 'b' + asrc cacheTuple = (asrc, kCurrent.tonicPitchNameWithCase) if USE_RN_CACHE and cacheTuple in rnKeyCache: #print "Got a match: " + str(cacheTuple) # Problems with Caches not picking up pivot chords... Not faster, see below. rn = copy.deepcopy(rnKeyCache[cacheTuple]) else: #print "No match for: " + str(cacheTuple) rn = roman.RomanNumeral( asrc, copy.deepcopy(kCurrent)) rnKeyCache[cacheTuple] = rn # surprisingly, not faster... and more dangerous #rn = roman.RomanNumeral(asrc, kCurrent) ## SLOWEST!!! #rn = roman.RomanNumeral(asrc, kCurrent.tonicPitchNameWithCase) #>>> from timeit import timeit as t #>>> t('roman.RomanNumeral("IV", "c#")', 'from music21 import roman', number=1000) #45.75 #>>> t('roman.RomanNumeral("IV", k)', 'from music21 import roman, key; k = key.Key("c#")', number=1000) #16.09 #>>> t('roman.RomanNumeral("IV", copy.deepcopy(k))', 'from music21 import roman, key; import copy; k = key.Key("c#")', number=1000) #22.49 ## key cache, does not help much... #>>> t('copy.deepcopy(r)', 'from music21 import roman; import copy; r = roman.RomanNumeral("IV", "c#")', number=1000) #19.01 if setKeyChangeToken is True: rn.followsKeyChange = True setKeyChangeToken = False else: rn.followsKeyChange = False except (roman.RomanNumeralException, common.Music21CommonException): #environLocal.printDebug('cannot create RN from: %s' % a.src) rn = note.Note() # create placeholder if pivotChordPossible == False: # probably best to find duration if previousChordInMeasure is None: pass # use default duration else: # update duration of previous chord in Measure oPrevious = previousChordInMeasure.getOffsetBySite( m) newQL = o - oPrevious if newQL <= 0: raise RomanTextTranslateException( 'too many notes in this measure: %s' % t.src) previousChordInMeasure.quarterLength = newQL rn.addLyric(prefixLyric + a.src) prefixLyric = "" m._insertCore(o, rn) previousChordInMeasure = rn previousRn = rn pivotChordPossible = True else: previousChordInMeasure.lyric += "//" + prefixLyric + a.src previousChordInMeasure.pivotChord = rn prefixLyric = "" pivotChordPossible = False elif isinstance(a, rtObjects.RTRepeat): if o == 0: if isinstance(a, rtObjects.RTRepeatStart): m.leftBarline = bar.Repeat( direction='start') else: rtt = RomanTextUnprocessedToken(a) m._insertCore(o, rtt) elif tsCurrent is not None and ( tsCurrent.barDuration.quarterLength == o or i == numberOfAtoms - 1): if isinstance(a, rtObjects.RTRepeatStop): m.rightBarline = bar.Repeat( direction='end') else: rtt = RomanTextUnprocessedToken(a) m._insertCore(o, rtt) else: # mid measure repeat signs rtt = RomanTextUnprocessedToken(a) m._insertCore(o, rtt) else: rtt = RomanTextUnprocessedToken(a) m._insertCore(o, rtt) #environLocal.warn("Got an unknown token: %r" % a) # may need to adjust duration of last chord added if tsCurrent is not None: previousRn.quarterLength = tsCurrent.barDuration.quarterLength - o m.elementsChanged() p._appendCore(m) except Exception: import traceback tracebackMessage = traceback.format_exc() raise RomanTextTranslateException( "At line %d for token %r, an exception was raised: \n%s" % (t.lineNumber, t, tracebackMessage)) p.elementsChanged() fixPickupMeasure(p) p.makeBeams(inPlace=True) p.makeAccidentals(inPlace=True) _addRepeatsFromRepeatEndings(p, repeatEndings) # 1st and second endings... s.insert(0, p) return s
def testIgnoresInversion(self): self.assertEqual(romanToFunction(roman.RomanNumeral('i6')), 't')
def simplify_chord( rn: Union[roman.RomanNumeral, str], hauptHarmonicFunction: bool = False, fullHarmonicFunction: bool = False, ignoreInversion: bool = False, ignoreRootAlt: bool = False, ignoreOtherAlt: bool = True, ): """ Given a chord (expressed as a roman.RomanNumeral or figure str), simplify that chord in one or more ways: hauptHarmonicFunction: returns the basic function like T, S, D. E.g., 'I' becomes 'T'. See notes at music21.analysis.harmonicFunction fullHarmonicFunction: Likewise, but supports Nebenfunktionen, e.g. Tp. E.g., 'vi' in a major key becomes 'Tp'. Again, see notes at music21.analysis.harmonicFunction ignoreInversion: E.g., '#ivo65' becomes '#ivo'. ignoreRootAlt: E.g., '#ivo' becomes 'ivo'. (NB: Not usually desirable!) ignoreOtherAlt: Ignore modifications of and added, removed, or chromatically altered tones. So '#ivo[add#6]' becomes '#ivo'. These are all settable separately, and so potentially combinable, but note that there is a hierarchy here. For instance, anything involving the function labels currently makes all the others reductant. >>> rn = roman.RomanNumeral('#ivo65[add4]/v') >>> rn.figure '#ivo65[add4]/v' >>> rn.primaryFigure '#ivo65[add4]' >>> rn = roman.RomanNumeral('#ivo65[add4]') >>> rn.romanNumeral '#iv' >>> rn.romanNumeralAlone 'iv' """ if isinstance(rn, str): rn = roman.RomanNumeral(rn) if hauptHarmonicFunction or fullHarmonicFunction: return hf.romanToFunction(rn, hauptHarmonicFunction=hauptHarmonicFunction) if any([ignoreInversion, ignoreRootAlt, ignoreOtherAlt]): # TODO fully, separate handling. Make a convenience fx in m21 # See doc notes above on .romanNumeral, .romanNumeralAlone, etc # HOw to handle secondary? rn.primaryFigure - follow m21 conventions # ignoreAdded = rn.romanNumeral # ignore # vs .figure vs .romanNumeralAlone if ignoreRootAlt: return rn.romanNumeralAlone else: if ignoreOtherAlt: # added etc., but keep root alt return rn.romanNumeral
def expand(self, ts=None, ks=None): ''' The meat of it all -- expand one rule completely and return a list of Measure objects. ''' if ts is None: ts = meter.TimeSignature('4/4') if ks is None: ks = key.Key('C') measures = [] lastRegularAtom = None lastChord = None for content, sep, numReps in self._measureGroups(): lastChordIsInSameMeasure = False if sep == "$": if content not in self.parent.rules: raise CTRuleException( "Cannot expand rule {0} in {2}".format(content, self)) rule = self.parent.rules[content] for i in range(numReps): returnedMeasures = rule.expand(ts, ks) self.insertKsTs(returnedMeasures[0], ts, ks) for m in returnedMeasures: tsEs = m.iter.getElementsByClass('TimeSignature') for returnedTs in tsEs: if returnedTs is not ts: # the TS changed mid-rule; create a new one for return. ts = copy.deepcopy(ts) measures.extend(returnedMeasures) elif sep == "|": m = stream.Measure() atoms = content.split() # key/timeSig pass... regularAtoms = [] for atom in atoms: if atom.startswith('['): atomContent = atom[1:-1] if atomContent == '0': ts = meter.TimeSignature('4/4') # irregular meter. Cannot fully represent; #TODO: replace w/ senza misura when possible. elif '/' in atomContent: # only one key / ts per measure. ts = meter.TimeSignature(atomContent) else: ks = key.Key( key.convertKeyStringToMusic21KeyString( atomContent)) elif atom == '.': if lastRegularAtom is None: raise CTRuleException(" . w/o previous atom: %s" % self) regularAtoms.append(lastRegularAtom) elif atom in ("", None): pass else: regularAtoms.append(atom) lastRegularAtom = atom numAtoms = len(regularAtoms) if numAtoms == 0: continue # maybe just ts and ks setting self.insertKsTs(m, ts, ks) atomLength = common.opFrac(ts.barDuration.quarterLength / numAtoms) for atom in regularAtoms: if atom == 'R': rest = note.Rest(quarterLength=atomLength) lastChord = None lastChordIsInSameMeasure = False m.append(rest) else: atom = self.fixupChordAtom(atom) rn = roman.RomanNumeral(atom, ks) if self.isSame(rn, lastChord) and lastChordIsInSameMeasure: lastChord.duration.quarterLength += atomLength m.elementsChanged() else: rn.duration.quarterLength = atomLength self.addOptionalTieAndLyrics(rn, lastChord) lastChord = rn lastChordIsInSameMeasure = True m.append(rn) measures.append(m) for i in range(1, numReps): measures.append(copy.deepcopy(m)) else: environLocal.warn( "Rule found without | or $, ignoring: '{0}','{1}': in {2}". format(content, sep, self.text)) #pass if len(measures) > 0: for m in measures: noteIter = m.recurse().notes if (noteIter and (self.parent is None or self.parent.labelSubsectionsOnScore is True) and self.LHS != 'S'): rn = noteIter[0] lyricNum = len(rn.lyrics) + 1 rn.lyrics.append(note.Lyric(self.LHS, number=lyricNum)) break return measures
def testRomanNumeralObjects(self): self.assertEqual(figureToFunction(roman.RomanNumeral('IV')), 'S') self.assertEqual(figureToFunction(roman.RomanNumeral('iv')), 's')