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
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))