Algorithm for generating pitch-class sets in prime form

J

John O'Hagan

I'm looking for some help coming up with an algorithm to produce lists which
meet the following criterion (you don't need to know music to get this):

In musical pitch-class set theory, "prime form" is defined as the most tightly-
packed-to-the-left rotation of a set's interval structure. Prime forms are
important in music because they provide the unique simplest form of any pitch
structure. I'm writing a python program which uses this principle.

For example: the sequence [2, 6, 9] (which happens to be a D major chord)
would be put in prime form by:

- Sorting and transposing it so it starts on zero: [0, 4, 7]
- Expressing it as intervals: [4, 3, 5] (the last number is the distance to
the octave)
- Rotating the intervals such that the biggest interval is at the end; if
there are more than one biggest intervals, we want the rotation with the
smallest first interval, and if that is also tied, the one with the smallest
second interval, and so on. In this example we are already there.

An easy way of finding a prime form in Python is:

def prime_form(seq):
pcs = sorted(list(set(seq)))
intervals = ([pcs[i + 1] - pcs for i in range(len(pcs) - 1)] +
[12 + min(pcs) - pcs[-1]])
rotations = [intervals[i:] + intervals[:i] for i in range(len(intervals))]
prime_intervals = min([i for i in rotations if i[-1] == max(intervals)])
return [sum(prime_intervals[:i]) for i in range(len(prime_intervals))]

But I'm looking for a way of generating sequences already in prime form
without duplication or omission. One promising approach is using a function
which generates all the (ordered) partitions of a number, and producing the
multiset permutations of each partition, which gives us all the possible
interval structures (but many of which are rotationally equivalent):

def full(num):
for part in partitions(num):
for perm in multiset_permutations(part):
yield perm

(The actual functions I'm using for this are at the end of this message.)

To start narrowing it down to prime forms, we can chop off the largest interval
from the end, permute the rest, and replace it on the end of each permutation:

def full(num):
for part in partitions(num):
if len(part) == 1:
yield part
else:
for perm in multiset_permutations(part[:-1]):
yield perm + part[-1]

but this produces a lot of false positives in cases where there is more than
one largest interval.

I imagine a solution might work by removing the largest intervals from each
partition, permuting the remaining intervals, and into each permutation
inserting the large intervals, one at the end and the others in each possible
place that satisfies the requirements. And that's where I'm getting lost.

Any clues, suggestions or solutions?

Regards,

John


The functions:

def partitions(num):
if num == 0:
yield []
return
for part in partitions(num-1):
yield [1] + part
if part and (len(part) < 2 or part[1] > part[0]):
yield [part[0] + 1] + part[1:]

def _reverse(seq, start, end):
"A sub-function for multiset_permutations"
#seq = seq[:start] + reversed(seq[start:end]) + seq[end:]
end -= 1
if end <= start:
return
while True:
seq[start], seq[end] = seq[end], seq[start]
if start == end or start+1 == end:
return
start += 1
end -= 1

def multiset_permutations(seq):
first = 0
last = len(seq)
yield seq
if last == 1:
raise StopIteration
while True:
next = last - 1
while True:
next1 = next
next -= 1
if seq[next] < seq[next1]:
mid = last - 1
while seq[next] >= seq[mid]:
mid -= 1
seq[next], seq[mid] = seq[mid], seq[next]
_reverse(seq, next1, last)
yield seq
break
if next == first:
raise StopIteration
raise StopIteration
 
J

John O'Hagan

Hi-

Do you knowof Christopher Ariza's AthenaCL?

<http://www.flexatone.net/athenaInfo.html#athenaFeatAnalytic>

HTH, Charles

Wow. That looks like a much more complete and elaborate version of what I'm
doing, although from what I can tell I don't think it's real-time. I'll
certainly be having a good poke around in the code. Thanks.

Mind you, rather than using an algorithm like the one one I was looking for in
the OP, AthenaCL uses a dictionary to hold the 12-based prime forms. I want to
use arbitrary base-numbers ("temperaments" if applied to pitch), so I'd like
to be able to generate the prime forms on the fly.

Regards,

John
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top