Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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())
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
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()
Exemplo n.º 6
0
    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])
Exemplo n.º 7
0
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
Exemplo n.º 8
0
 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')
Exemplo n.º 9
0
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
Exemplo n.º 10
0
    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')
Exemplo n.º 11
0
    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'))
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
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.'
        )
Exemplo n.º 14
0
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
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
    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(
        )
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
 def testIsOfType(self):
     self.assertTrue(isOfType(roman.RomanNumeral('I'), [6, 10, 1]))
     self.assertTrue(isOfType(chord.Chord('G- B-- D-'), '3-11A'))
Exemplo n.º 19
0
    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
Exemplo n.º 20
0
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
Exemplo n.º 21
0
 def testIgnoresInversion(self):
     self.assertEqual(romanToFunction(roman.RomanNumeral('i6')), 't')
Exemplo n.º 22
0
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
Exemplo n.º 23
0
    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
Exemplo n.º 24
0
 def testRomanNumeralObjects(self):
     self.assertEqual(figureToFunction(roman.RomanNumeral('IV')), 'S')
     self.assertEqual(figureToFunction(roman.RomanNumeral('iv')), 's')