Created
March 2, 2015 12:33
-
-
Save augeas/1a74e3363fd48716f9e9 to your computer and use it in GitHub Desktop.
Here's Looking at Euclid...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Licensed under the Apache License Version 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt | |
# Here's Looking at Euclid. Giles R. Greenway, March 2015. | |
# Euclidian poly-rhythms... | |
import itertools | |
# http://www.pygame.org/docs/ref/midi.html | |
import pygame | |
from pygame import midi | |
from pygame import time | |
# http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf | |
def euclid_rhythm(n,k): | |
if k > n: | |
n,k = k,n | |
seq = [ [True] for i in range(k) ] + [ [False] for j in range(n-k) ] | |
while seq[-1] == seq[-2]: # Are there multiple copies of the same sequence at the end? | |
last = seq[-1] | |
for item in seq: | |
if item == last: | |
break # Run out of things to tack the sequence on to... | |
else: | |
if seq[-1] == last: | |
item += seq.pop() | |
else: | |
break # Run out of copies of the sequence. | |
return [ i for i in itertools.chain(*seq) ] | |
# Phase rhythms in and out according to a three-bit Grey code. | |
gray = [ [0,0,1], [0,1,1], [0,1,0], [1,1,0], [1,1,1], [1,0,1], [1,0,0] ] | |
# Return the ith Gray code for j beats. | |
def masker(i,j): | |
for t in range(j): | |
yield gray[i] | |
# Three rhythms, as the Korg Volca Keys has three voices. It's amusing to ring-mod them together. | |
# 4, 9 & 25 are the lowest triplet of relatively prime intergers, the first three primes squared. | |
pairs = [ (4,9), (9,25), (4,25) ] | |
# Lengths of each rhythm. | |
lengths = [ max(i) + abs(i[0]-i[1]) for i in pairs ] | |
# Number of beats given to each combination of rhythms given by the number of rhythms present, | |
# multiplied by the sum of their lengths. | |
dur = [ sum(gray[j]) * sum([ lengths[i] for i in gray[j] ]) for j in range(7) ] | |
# Iterate over the combination of rhythms at each beat. | |
masks = itertools.chain(*[ masker(i,dur[i]) for i in range(7) ]) | |
# Three iterators to cycle through each rhythm. | |
voices = [ itertools.cycle(euclid_rhythm(*v)) for v in pairs ] | |
freqs = [ 30, 60, 120 ] | |
intervals = [ 4, 2, 1 ] | |
counts = [ 0, 0, 0 ] | |
pygame.init() | |
midi.init() | |
# Gratuitously hard-coded MIDI channel... | |
midi_out = midi.Output(2) | |
for mask in masks: | |
beats = [ v.next() for v in voices ] | |
# Each note is the base pitch for each rhythm, plus the time since the last beat multiplied by an interval. | |
notes = [ (freqs[i] + intervals[i] * counts[i]) & 127 for i in range(3) if beats[i] and mask[i] ] | |
for i in range(3): | |
if beats[i]: | |
counts[i] = 0 | |
else: | |
counts[i] += 1 | |
for i in notes: | |
midi_out.note_on(i,127) | |
time.delay(200) | |
for i in notes: | |
midi_out.note_off(i,127) | |
midi_out.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment