CopeEMIFunctions.py

#

Functions from Cope's 1992 article in the Computer Music Journal, "Modeling of Musical Intelligence in EMI" translated into Python. Actual code is here.

motive_size = 2
variance = 1
#

Used by allow-variation. motive1 and motive2 are lists of integers representing directed pitch intervals.

Compares motive1 with original, inversion, retrograde, and retrograde inversion of motive2. If the absolute pairwise differences for any of these four comparisons are all less than or equal to the variance, returns True; otherwise, returns False.

def allow_var(motive1, motive2):
#
    min_length = min(len(motive1), len(motive2))
    comparisons = [motive2, inversion(motive2), retrograde(motive2),
                   inversion(retrograde(motive2))]
    for comparison in comparisons:
        for i in range(min_length):
            if abs(motive1[i] - comparison[i]) > variance:
                break
        else:
            return True
    return False
#

Returns the inversion of motive.

def inversion(motive):
#
    return [-el for el in motive]
#

Returns the retrograde of motive.

def retrograde(motive):
#
    return motive[::-1]
#

motive is a list of integers representing directed pitch intervals. motive_list is a list of such motives.

For each comparison in motive_list, calls all_var to see if comparison matches motive. For successful matches, both motive and comparison are added to signatures.

Returns signatures, a list of successful matches.

def allow_variation(motive, motive_list):
#
    signatures = []
    for comparison in motive_list:
        if allow_var(motive, comparison):
            signatures.extend([motive, comparison])
    return signatures
#

work is a list of integers representing pitches.

Returns a list of intervals.

def produce_intervals(work):
#
    return [work[i] - work[i-1] for i in range(1, len(work))]
#

intervals is a list of integers. Returns a list of ngrams of intervals, where n = size.

def break_into_patterns(intervals, size):
#
    return [intervals[i:i+size] for i in range(len(intervals)-size+1)]
#

Matches patterns for pattern_match. Steps through the motives in interval_lists1 for comparisons with interval_lists2 by calling allow_variation.

def match(interval_lists1, interval_lists2):
#
    signatures = []
    for intervals in interval_lists1:
        signatures.extend(allow_variation(intervals, interval_lists2))
    return signatures
#

work1 and work2 are lists of integers representing sequences of pitches.

Matches the works under analysis after they have been reduced to intervals and broken into motives the length of motive_size. The process used by break_into_patterns is thorough in that it finds every contiguous pattern of the length prescribed by motive_size so that, for example, [1, 2, 3, -4, -5] becomes [[1, 2, 3], [2, 3, -4], [3, -4, -5]]. Note that using intervals at this stage means that a motive_size of two equates to three actual notes.

def pattern_match(work1, work2):
#
    intervals1 = break_into_patterns(produce_intervals(work1), motive_size)
    intervals2 = break_into_patterns(produce_intervals(work2), motive_size)
    signatures = match(intervals1, intervals2)
    return signatures
#

Example motive and motivelist

motive = [4, 3]
motivelist = [[0, 0], [0, 0], [0, -4], [-4, -3], [-3, 0]]
print('motive:', motive)
print('motive list:', motivelist)
#

Signatures discovered between motive and motivelist using allow_varation

print('motive in motive list?', allow_variation(motive, motivelist))
print()
#

Example works

work1 = [72, 76, 79, 71, 72, 74, 72]
work2 = [76, 76, 76, 76, 72, 69, 69, 68, 68, 74, 71]
print('work 1 pitches:', work1)
print('work 2 pitches:', work2)
#

Signatures derived from comparison of the two works

print('patterns in common:', pattern_match(work1, work2))