Saturday, January 31, 2009

Python 3.0 / Hex On!

This week, I've started playing with Python 3.0 (aka Py3k). The final 3.0 version had been released in December, so there is only a release candidate available in the Intrepid repositories, but I didn't care. In fact, it was dead easy to install - simply apt-get install python3 and you're all set!

Among the things, that amazed me the most, is a new fractions module (actually existing since Python 2.6), that provides support for rational numbers! What are rational numbers good for? Well, they're invaluable for pretty much anything, that involves just intonation, to say the least!

For example, here's how I've managed to code a simple Hexany generator in almost no time! Roughly speaking, a hexany is created from several prime (or not so prime) numbers, that are multiplied together in pairs to form a set. To get a scale out of it, the resulting numbers should be divided by a chosen "base note" and reduced to the octave range. So, first of all, we'll need a simple octave reduction function:
def octave_range(fr):
    if fr <= 0: raise ValueError("Invalid frequency ratio")
    elif fr > 2: return octave_range(fr / 2)
    elif fr < 1: return octave_range(fr * 2)
    else: return fr
Now, let's define a "base note" and use set comprehension (another Py3k feature) to fill the CPS with permutations of numbers from the Wikipedia example:
nums = [2, 3, 5, 7]
base = 5 * 7
cmps = {a * b for a in nums for b in nums if a != b}
And thanks to the fractions support, the final step is also going to be the easiest:
from fractions import Fraction
hexany = [octave_range(Fraction(note, base)) for note in cmps]
The code is also suitable for producing dekanies and other scales, based on Wilson's combination product sets. I still have to figure out the proper ways of using them in music though :P

1 comment:

  1. my project is based on ji so .. this info was really great .. i've just moved to 2.6 so must try ur example
    on 2.5 i was doing something like this to keep the freq in a certain range but urs look cleaner than mine

    self.freq = self.freq * 0.5 ** math.floor(math.log((self.freq / 20.0),2))