1

I'm building a musical trainer game where each after 12 pitch classes [C,CD,D,DE,E,F,FG,G,GA,A,AB,B] is represented by [0 thru 11], and a chord is a list of such numbers, so CEG would be [0,4,7]

Looking at the difference gives us the intervals: [4-0=4, 7-4=3] so [4,3], a minor third stacked on top of a major third in this case.

Suppose I have a complete list of all k-note chords, formed by maybe doing:

pitchClasses = [i for i in range(12)]

from itertools import combinations
deck = list( combinations(pitchClasses, k) )

How can I prune my deck so as to reject any chord containing an interval < p or > q?

Say for example I set p=3,q=4. then [C,D,G] gets excluded because it is [0,2,7] with gaps [2,5] and 2<3. Also 5>4 so it gets excluded on two counts.

Also [B,C] would get excluded because [11,0] -> gap of [1]

CEG wouldn't get excluded ; it has gaps [4,3]. There is also a gap of 7 between the G and wrapping round to the C, but this wouldn't constitute rejection.

It would be cleaner if a 3-note chord produces a 'dual' of 3 gaps. Because the wraparound gap might not be the biggest gap.

Then the smallest element can be compared with p, and the second-largest element can be compared with q.

I believe I would need to create the 'dual' gaps-list, order it smallest first, and then compare:

if W[0] < p or W[-2] > q:
    # reject

I've done this in C# and it resulted in rather a lot of code.

Is there any way to do this concisely in Python?

So far I've found:

How can I find all the subsets of a set, with exactly n elements?
Python - Differences between elements of a list

And my code looks like this so far:

from random import shuffle
from itertools import combinations

pitchClasses = [i for i in range(12)]

deck = list( combinations(pitchClasses, notesInChord) )

def GapOk(chord,min_gap,max_gap):
    gaps = [ ( 12 + chord[i+1]-chord[i] ) % 12  for  i in range(len(chord)) ]
    intervals = sorted( gaps )
    return intervals[0] >= min_gap  and  intervals[-2] <= max_gap

deck_pruned = [ chord for chord in deck if GapOk(chord,min_gap,max_gap) ]

shuffle( deck_pruned )

Well, it looks as though I may have answered my own question. But I'm going to put it up anyway, because I'm very interested whether there is a better way of doing it. I may break the above code-block out into an answer later.

Community
  • 1
  • 1
P i
  • 29,020
  • 36
  • 159
  • 267
  • Might be better migrated to Code Review – Russia Must Remove Putin Jun 01 '14 at 23:10
  • That's definitely the right thing to do. I've not yet used Code Review -- I will jump on it now. Not sure if I can migrate my own question or if I should close it and copy/paste. – P i Jun 01 '14 at 23:36
  • I flagged it and asked a moderator to migrate it, I think it's doable. – Russia Must Remove Putin Jun 01 '14 at 23:47
  • @Pi: CR mod here. You're just asking how to write this present code more concisely, correct? By some of the wording, it kind of sounds like you're asking for additional implementation. We can only review *working* code as it's written, so if that's the case here, I'll get it migrated. – Jamal Jun 02 '14 at 14:39

1 Answers1

1

I'll give the code review a go:

# prefer to use full paths for people less familiar
# from random import shuffle 
# from itertools import combinations # with your libs
import random
import itertools 


# pitchClasses = [i for i in range(12)] # just materialize into list:
# _ preferred to camelcase for non-native-English readers
pitch_classes = list(range(12)) 

# no need for extra spaces inside container chars, format like English
# deck = list(itertools.combinations(pitchClasses, notesInChord))
# maybe 3, 4, & 5 note chords?
chord_numbers = (3, 4, 5)  
deck = []
for notes_in_chord in chord_numbers:
    deck.extend(itertools.combinations(pitch_classes, notes_in_chord))

def GapOk(chord, min_gap, max_gap):
    # not sure what you're doing here, so I'll leave off for now, revisit later
    # for now, suggest docstring with explanation
    gaps = [(12 + chord[i+1]-chord[i]) % 12 for i in range(len(chord))]
    intervals = sorted(gaps)
    return intervals[0] >= min_gap and intervals[-2] <= max_gap

deck_pruned = [chord for chord in deck if GapOk(chord, min_gap, max_gap)]

random.shuffle(deck_pruned)
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331