The main idea here is that n
onsets spread as evenly as possible over k
slots generates very nice rhythms that can be found in music from all over the world. The original paper noted that these rhythms are generated by invocations of the Euclidean algorithm, however I think that observation unnecessarily complicates the technique. The original paper can be found here: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.1340&rep=rep1&type=pdf)
Obviously, this procedure is simple when gcd(n, k) != 1
. When the two parameters are coprime, however, things get interesting (and slightly more difficult to calculate).
Let E(n, k)
define such a rhythm for positive integers n
, k
. In a rhythmic sequence, let .
denote a rest and x
denote an onset. Here are some examples of Euclidean rhythms:
E(5, 6) = xxxxx.
E(2, 6) = x..x..
E(5, 16) = x..x..x..x..x...
Some observations are as follows:
n
and k
are coprime, things tend to be much more interesting. The rhythm E(5, 16)
is more fun to listen to than the rhythm E(4, 16)
E(4, 16)
actually reduces to the rhythm E(1, 4)
played four times over a time period four times shorter (perhaps now you can see where the Euclidean algorithm comes into play here). This leads to an interesting observation—rhythms when n
and k
are coprime serve as the fundamental atomic units of the set of all rhythms, much like prime numbers in the universe of integers (multiplicatively speaking, at least). Let’s call such a coprime rhythm a fundamental rhythm.k=4
, one for k=8
, one for k=16
, et cetera.n
distinct rotations, all of which define different rhythms. If we remove this restriction, then a fundamental rhythm has up to k
distinct rotations (all of which, once again, define different rhythms). The original paper defines such a collection of k
rhythms corresponding to a certain E(n, k)
as a necklace.
import math
import random
import sys
rotate = True ## TODO add rotation
bpm = 100
def gen(n, k, bars):
step = k / n
out_bin = [0] * (k * bars)
out = []
for i in range(n * bars):
out_bin[round(step * i)] = 1
out.append((round(step * i)) * ((bpm / 60.0) / k))
print(out_bin)
print(out)
return out
def random_pair(k):
return random.randint(1, k), k
def main():
in_data = []
sequences = []
print(len(sys.argv))
flag = random.randint(0, 1)
r1 = [3, 4]
r2 = [6, 8]
r3 = [12, 16]
r4 = [6, 8]
if len(sys.argv) == 1:
in_data.append("")
sequence = random_pair(r1[flag])
print(sequence)
in_data.append(sequence[0])
in_data.append(sequence[1])
sequence = random_pair(r2[flag])
print(sequence)
in_data.append(sequence[0])
in_data.append(sequence[1])
sequence = random_pair(r3[flag])
print(sequence)
in_data.append(sequence[0])
in_data.append(sequence[1])
sequence = random_pair(r4[flag])
sequence = ((int) (sequence[0] / 3) + 1, sequence[1])
print(sequence)
in_data.append(sequence[0])
in_data.append(sequence[1])
# sequence = random_pair(24)
# print(sequence)
# in_data.append(sequence[0])
# in_data.append(sequence[1])
else:
in_data = sys.argv
for i in range(1, len(in_data), 2):
sequences.append(gen(int(in_data[i]), int(in_data[i + 1]), 8))
f = open("polyrhythm.synthSequence", "w")
freq_options = [58, 62, 65, 67, 73, 78, 82, 87]
freq = random.choice(freq_options)
amplitude = 0.7
for sequence in sequences:
for i in sequence:
f.write("@ {} 0.05 SineEnv {} {} 0.01 0.1 0.0\n".format(i, amplitude, freq))
print("@ {} 0.05 SineEnv {} {} 0.01 0.1 0.0".format(i, amplitude, freq))
f.write("\n")
print()
freq = freq * 2
amplitude = amplitude * 0.7
f.close()
main()