Sharing Code: Polyrhythm

Hello everyone!

I’d like to share some code I wrote, and tell a little story.

Do you know polyrhythms? It’s really fun to play them with your hands on random sound-making things.

I wanted to practice them and watched some cool Videos by Adam Neely to get into it again:

After some practicing of the three-against-four (3:4) rhythm I had an idea:
Let’s say, we play the slower 1/3 notes with the left hand and the faster 1/4 notes with the right hand.
Then we stop playing with the left had and just continue to play with the right hand.
The right hand continues in the same tempo for some time and we forget everything we played so far.
We just hear the steady beat. Now we interpret the movement of the right hand as 1/3 notes, but the tempo of the right hand is still the same. Then we play 1/4 notes with the left hand. The left hand is faster than the right hand, because 1/4 notes are faster than 1/3 notes.

Now we can repeat the whole process, but the roles of the right and left hand are switched.
That way we get faster with every new cycle – faster and faster.
To compensate that, we slowly get slower while playing with both hands.

I tried to put my idea into practice, but I failed. I thought: “I have to listen to this to practice it.” And the easiest method for doing this, was to implement it in Sonic-Pi. And that’s what I did.

The code can be found in this git repo on github: polyrhythm/three-against-four.rb at main · j0le/polyrhythm · GitHub

via the perma-link of the current version: polyrhythm/three-against-four.rb at 2187a2748134caf83456fdc2c42ce976350569ea · j0le/polyrhythm · GitHub

or here:

``````define :my_play do |bpm, sample_sym, repeats|
with_bpm bpm do
repeats.times do
sample sample_sym
sleep 1.0/repeats
end
end
end
end

repeats_x = 3
repeats_y = 4

state = 1
start_bpm = 40.0
end_bpm = start_bpm*1.0*repeats_x/repeats_y
bpm = start_bpm

sound_a = :perc_snap
sound_b = :perc_snap2

live_loop :polyrythm do
with_bpm bpm do

if state == 1

if bpm == start_bpm
sample :perc_bell, amp: 0.5
end

my_play bpm, sound_a, repeats_x
my_play bpm, sound_b, repeats_y

getting_faster = start_bpm < end_bpm

if (getting_faster && (bpm >= end_bpm)) || (!getting_faster && (bpm <= end_bpm))
state = 2
bpm = end_bpm
else
bpm += (end_bpm - start_bpm)/6.0
end

elsif state == 2
sample :perc_bell, amp: 0.5
sample sound_a
my_play bpm, sound_b, repeats_y

bpm = start_bpm
state=3

elsif state == 3
sample :perc_bell, amp: 0.5
my_play bpm, sound_b, repeats_x

# swap sounds
helper = sound_a
sound_a = sound_b
sound_b = helper

state=1
else
# alarm
my_play bpm*2, :perc_bell, 7

end

sleep 1
end
end
``````

It’s a state machine. What do you think of it?
Maybe I can use some other techniques. For example rings.

Anyway – I have some plans: I want to add some more states and fade the sounds in and out.

And that’s it for now. Maybe I add some more thoughts later.

— Ole

1 Like

The ’ use_bpm ’ and ’ live_loop ’ used together
make SPi a natural for polyrhythms …
i.e…

``````use_bpm A
Live_loop :a do
blah

use_bpm B
Live_loop :b do
blah
``````

Just for fun:

``````speedy = (line Math::log(90), Math::log(900), steps: 128).map {|x| Math.exp(x)}
loop do
sleep 1
end
end

CHORD = (
[55, 58, 62]*4+
[54, 60, 57, 62]*3+
[55, 58, 62]*4+
[53, 57, 60]*4+
[53, 58, 62]*4+
[53, 57, 60]*4+
[55, 58, 62]*4+
[60,54,57,62]*3
).ring
set :foo, 0

def gensym()
"#{tick(:g)}"
end

define :launch do |n|
live_loop gensym do
t = 4.0
synth :bass_foundation, amp: 0.6, note: CHORD[get(:foo)], sustain: 0.3, release: 0.1
set :foo, get(:foo)+1
sleep t/n
end
end

with_fx :flanger do
with_fx :reverb do
with_fx :bitcrusher, cutoff: 100 do
with_fx :pan, pan: -1 do
launch(4.05)
end
with_fx :pan, pan: 1 do
launch(3)
end
end
end
end

``````

You can get different rhythms by changing the parameters:

``````use_bpm 300
CHORD = (
[55, 58, 62]*4+
[50,54,57,60]*3+
[55, 58, 62]*4+
[53, 57, 60]*4+
[58, 53, 62]*4+
[53, 57, 60]*4+
[55, 58, 62]*4+
[50,54,57,60]*3
).ring
set :foo, 0

def gensym()
"#{tick(:g)}"
end

define :launch do |n|
live_loop gensym do
t = 4.0
synth :bass_foundation, amp: 0.6, note: CHORD[get(:foo)], sustain: 0.3, release: 0.1
set :foo, get(:foo)+1
sleep t/n
end
end

with_fx :flanger do
with_fx :reverb do
with_fx :bitcrusher, cutoff: 100 do
launch(6)
with_fx :pan, pan: -1 do
launch(6.01)
end
with_fx :pan, pan: 1 do
launch(3)
end
end
end
end
``````
``````use_bpm 100
CHORD = (
[55, 58, 62]*4+
[50,54,57,60]*3+
[55, 58, 62]*4+
[53, 57, 60]*4+
[58, 53, 62]*4+
[53, 57, 60]*4+
[55, 58, 62]*4+
[50,54,57,60]*3
).ring
set :foo, 0

def gensym()
"#{tick(:g)}"
end

define :launch do |n|
live_loop gensym do
t = 4.0
synth :bass_foundation, amp: 0.6, note: CHORD[get(:foo)], sustain: 0.3, release: 0.1
set :foo, get(:foo)+1
sleep t/n
end
end

with_fx :flanger do
with_fx :reverb do
with_fx :bitcrusher, cutoff: 100 do

with_fx :pan, pan: -1 do
launch(7.01)
end
with_fx :pan, pan: 1 do
launch(11.01)
end
end
end
end

``````

would you mind explain this part of you code ?

I just did not want to type out each `live_loop` separately, so repeatedly calling `gensym` (it’s just used as a kind of macro there) outputs `"0"`, `"1"`, etc. Therefore `launch(11)`, `launch(4)` creates `live_loop_0`, `live_loop_1` and so on, just a different name every time you run it. Since this is not really “live” code you are right, you do not necessarily need named `live_loops`.